/*!
 * # Semantic UI 2.2.3 - Dropdown
 * http://github.com/semantic-org/semantic-ui/
 *
 *
 * Released under the MIT license
 * http://opensource.org/licenses/MIT
 *
 */

;
(function ($, window, document, undefined) {
    "use strict";

    window = (typeof window != 'undefined' && window.Math == Math)
        ? window
        : (typeof self != 'undefined' && self.Math == Math)
                 ? self
                 : Function('return this')()
    ;

    $.fn.dropdown = function (parameters) {
        var
            $allModules = $(this),
            $document = $(document),

            moduleSelector = $allModules.selector || '',

            hasTouch = ('ontouchstart' in document.documentElement),
            time = new Date().getTime(),
            performance = [],

            query = arguments[0],
            methodInvoked = (typeof query == 'string'),
            queryArguments = [].slice.call(arguments, 1),
            returnedValue
            ;

        $allModules
            .each(function (elementIndex) {
                      var
                          settings = ( $.isPlainObject(parameters) )
                              ? $.extend(true, {}, $.fn.dropdown.settings, parameters)
                              : $.extend({}, $.fn.dropdown.settings),

                          className = settings.className,
                          message = settings.message,
                          fields = settings.fields,
                          keys = settings.keys,
                          metadata = settings.metadata,
                          namespace = settings.namespace,
                          regExp = settings.regExp,
                          selector = settings.selector,
                          error = settings.error,
                          templates = settings.templates,

                          eventNamespace = '.' + namespace,
                          moduleNamespace = 'module-' + namespace,

                          $module = $(this),
                          $context = $(settings.context),
                          $text = $module.find(selector.text),
                          $search = $module.find(selector.search),
                          $sizer = $module.find(selector.sizer),
                          $input = $module.find(selector.input),
                          $icon = $module.find(selector.icon),

                          $combo = ($module.prev().find(selector.text).length > 0)
                              ? $module.prev().find(selector.text)
                              : $module.prev(),

                          $menu = $module.children(selector.menu),
                          $item = $menu.find(selector.item),

                          activated = false,
                          itemActivated = false,
                          internalChange = false,
                          element = this,
                          instance = $module.data(moduleNamespace),

                          initialLoad,
                          pageLostFocus,
                          willRefocus,
                          elementNamespace,
                          id,
                          selectObserver,
                          menuObserver,
                          module;

                      module = {

                          initialize: function () {
                              module.debug('Initializing dropdown', settings);
                              if (module.is.alreadySetup()) {
                                  module.setup.reference();
                              }
                              else {
                                  module.setup.layout();
                                  module.refreshData();

                                  module.save.defaults();
                                  module.restore.selected();

                                  module.create.id();
                                  module.bind.events();

                                  module.observeChanges();
                                  module.instantiate();
                              }
                              if (settings.finish) {
                                  settings.finish.call($module, module);
                              }
                          },

                          instantiate: function () {
                              module.verbose('Storing instance of dropdown', module);
                              instance = module;
                              $module
                                  .data(moduleNamespace, module)
                              ;
                          },

                          destroy: function () {
                              module.verbose('Destroying previous dropdown', $module);
                              module.remove.tabbable();
                              $module
                                  .off(eventNamespace)
                                  .removeData(moduleNamespace)
                              ;
                              $menu
                                  .off(eventNamespace)
                              ;
                              $document
                                  .off(elementNamespace)
                              ;
                              module.disconnect.menuObserver();
                              module.disconnect.selectObserver();
                          },

                          observeChanges: function () {
                              if ('MutationObserver' in window) {
                                  selectObserver =
                                  new MutationObserver(module.event.select.mutation);
                                  menuObserver = new MutationObserver(module.event.menu.mutation);
                                  module.debug('Setting up mutation observer', selectObserver,
                                               menuObserver);
                                  module.observe.select();
                                  module.observe.menu();
                              }
                          },

                          disconnect: {
                              menuObserver: function () {
                                  if (menuObserver) {
                                      menuObserver.disconnect();
                                  }
                              },
                              selectObserver: function () {
                                  if (selectObserver) {
                                      selectObserver.disconnect();
                                  }
                              }
                          },
                          observe: {
                              select: function () {
                                  if (module.has.input()) {
                                      selectObserver.observe($input[0], {
                                          childList: true,
                                          subtree: true
                                      });
                                  }
                              },
                              menu: function () {
                                  if (module.has.menu()) {
                                      menuObserver.observe($menu[0], {
                                          childList: true,
                                          subtree: true
                                      });
                                  }
                              }
                          },

                          create: {
                              id: function () {
                                  id = (Math.random().toString(16) + '000000000').substr(2, 8);
                                  elementNamespace = '.' + id;
                                  module.verbose('Creating unique id for element', id);
                              },
                              userChoice: function (values) {
                                  var
                                      $userChoices,
                                      $userChoice,
                                      isUserValue,
                                      html
                                      ;
                                  values = values || module.get.userValues();
                                  if (!values) {
                                      return false;
                                  }
                                  values = $.isArray(values)
                                      ? values
                                      : [values]
                                  ;
                                  $.each(values, function (index, value) {
                                      if (module.get.item(value) === false) {
                                          html =
                                          settings.templates.addition(
                                              module.add.variables(message.addResult, value));
                                          $userChoice = $('<div />')
                                              .html(html)
                                              .attr('data-' + metadata.value, value)
                                              .attr('data-' + metadata.text, value)
                                              .addClass(className.addition)
                                              .addClass(className.item)
                                          ;
                                          if (settings.hideAdditions) {
                                              $userChoice.addClass(className.hidden);
                                          }
                                          $userChoices = ($userChoices === undefined)
                                              ? $userChoice
                                              : $userChoices.add($userChoice)
                                          ;
                                          module.verbose('Creating user choices for value', value,
                                                         $userChoice);
                                      }
                                  });
                                  return $userChoices;
                              },
                              userLabels: function (value) {
                                  var
                                      userValues = module.get.userValues()
                                      ;
                                  if (userValues) {
                                      module.debug('Adding user labels', userValues);
                                      $.each(userValues, function (index, value) {
                                          module.verbose('Adding custom user value');
                                          module.add.label(value, value);
                                      });
                                  }
                              },
                              menu: function () {
                                  $menu = $('<div />')
                                      .addClass(className.menu)
                                      .appendTo($module)
                                  ;
                              },
                              sizer: function () {
                                  $sizer = $('<span />')
                                      .addClass(className.sizer)
                                      .insertAfter($search)
                                  ;
                              }
                          },

                          search: function (query) {
                              query = (query !== undefined)
                                  ? query
                                  : module.get.query()
                              ;
                              module.verbose('Searching for query', query);
                              if (module.has.minCharacters(query)) {
                                  module.filter(query);
                              }
                              else {
                                  module.hide();
                              }
                          },

                          select: {
                              firstUnfiltered: function () {
                                  module.verbose('Selecting first non-filtered element');
                                  module.remove.selectedItem();
                                  var its = $item
                                      .not(selector.unselectable)
                                      .not(selector.addition + selector.hidden);
                                  var selectValue = module.get.value();
                                  //将当前选择框的值设置成选择状态
                                  if (selectValue) {
                                      var its2 = its.filter("[data-value='" + module.get.value()
                                                            + "']");
                                      if (its2.length != 0) {
                                          its = its2;
                                      }
                                  }
                                  its.eq(0).addClass(className.selected);
                              },
                              nextAvailable: function ($selected) {
                                  $selected = $selected.eq(0);
                                  var
                                      $nextAvailable = $selected.nextAll(selector.item).not(
                                          selector.unselectable).eq(0),
                                      $prevAvailable = $selected.prevAll(selector.item).not(
                                          selector.unselectable).eq(0),
                                      hasNext = ($nextAvailable.length > 0)
                                      ;
                                  if (hasNext) {
                                      module.verbose('Moving selection to', $nextAvailable);
                                      $nextAvailable.addClass(className.selected);
                                  }
                                  else {
                                      module.verbose('Moving selection to', $prevAvailable);
                                      $prevAvailable.addClass(className.selected);
                                  }
                              }
                          },

                          setup: {
                              api: function () {
                                  var
                                      apiSettings = {
                                          debug: settings.debug,
                                          urlData: {
                                              value: module.get.value(),
                                              query: module.get.query()
                                          },
                                          on: false
                                      }
                                      ;
                                  module.verbose('First request, initializing API');
                                  $module
                                      .api(apiSettings)
                                  ;
                              },
                              layout: function () {
                                  if ($module.is('select')) {
                                      module.setup.select();
                                      module.setup.returnedObject();
                                  }
                                  if (!module.has.menu()) {
                                      module.create.menu();
                                  }
                                  if (module.is.search() && !module.has.search()) {
                                      module.verbose('Adding search input');
                                      $search = $('<input />')
                                          .addClass(className.search)
                                          .prop('autocomplete', 'off')
                                          .insertBefore($text)
                                      ;
                                  }
                                  if (module.is.multiple() && module.is.searchSelection()
                                      && !module.has.sizer()) {
                                      module.create.sizer();
                                  }
                                  if (settings.allowTab) {
                                      module.set.tabbable();
                                  }
                              },
                              select: function () {
                                  var
                                      selectValues = module.get.selectValues()
                                      ;
                                  module.debug('Dropdown initialized on a select', selectValues);
                                  if ($module.is('select')) {
                                      $input = $module;
                                  }
                                  // see if select is placed correctly already
                                  if ($input.parent(selector.dropdown).length > 0) {
                                      module.debug(
                                          'UI dropdown already exists. Creating dropdown menu only');
                                      $module = $input.closest(selector.dropdown);
                                      if (!module.has.menu()) {
                                          module.create.menu();
                                      }
                                      $menu = $module.children(selector.menu);
                                      module.setup.menu(selectValues);
                                  }
                                  else {
                                      module.debug('Creating entire dropdown from select');
                                      $module = $('<div />')
                                          .attr('class', $input.attr('class'))
                                          .addClass(className.selection)
                                          .addClass(className.dropdown)
                                          .html(templates.dropdown(selectValues))
                                          .insertBefore($input)
                                      ;
                                      if ($input.hasClass(className.multiple)
                                          && $input.prop('multiple')
                                             === false) {
                                          module.error(error.missingMultiple);
                                          $input.prop('multiple', true);
                                      }
                                      if ($input.is('[multiple]')) {
                                          module.set.multiple();
                                      }
                                      if ($input.prop('disabled')) {
                                          module.debug('Disabling dropdown');
                                          $module.addClass(className.disabled);
                                      }
                                      $input
                                          .removeAttr('class')
                                          .detach()
                                          .prependTo($module)
                                      ;
                                  }
                                  module.refresh();
                              },
                              menu: function (values) {
                                  $menu.html(templates.menu(values, fields));
                                  $item = $menu.find(selector.item);
                              },
                              reference: function () {
                                  module.debug(
                                      'Dropdown behavior was called on select, replacing with closest dropdown');
                                  // replace module reference
                                  $module = $module.parent(selector.dropdown);
                                  module.refresh();
                                  module.setup.returnedObject();
                                  // invoke method in context of current instance
                                  if (methodInvoked) {
                                      instance = module;
                                      module.invoke(query);
                                  }
                              },
                              returnedObject: function () {
                                  var
                                      $firstModules = $allModules.slice(0, elementIndex),
                                      $lastModules = $allModules.slice(elementIndex + 1)
                                      ;
                                  // adjust all modules to use correct reference
                                  $allModules = $firstModules.add($module).add($lastModules);
                              }
                          },

                          refresh: function () {
                              module.refreshSelectors();
                              module.refreshData();
                          },

                          refreshItems: function () {
                              $item = $menu.find(selector.item);
                          },

                          refreshSelectors: function () {
                              module.verbose('Refreshing selector cache');
                              $text = $module.find(selector.text);
                              $search = $module.find(selector.search);
                              $input = $module.find(selector.input);
                              $icon = $module.find(selector.icon);
                              $combo = ($module.prev().find(selector.text).length > 0)
                                  ? $module.prev().find(selector.text)
                                  : $module.prev()
                              ;
                              $menu = $module.children(selector.menu);
                              $item = $menu.find(selector.item);
                          },

                          refreshData: function () {
                              module.verbose('Refreshing cached metadata');
                              $item
                                  .removeData(metadata.text)
                                  .removeData(metadata.value)
                              ;
                          },

                          clearData: function () {
                              module.verbose('Clearing metadata');
                              $item
                                  .removeData(metadata.text)
                                  .removeData(metadata.value)
                              ;
                              $module
                                  .removeData(metadata.defaultText)
                                  .removeData(metadata.defaultValue)
                                  .removeData(metadata.placeholderText)
                              ;
                          },

                          toggle: function () {
                              module.verbose('Toggling menu visibility');
                              if (!module.is.active()) {
                                  module.show();
                              }
                              else {
                                  module.hide();
                              }
                          },

                          show: function (callback) {
                              callback = $.isFunction(callback)
                                  ? callback
                                  : function () {
                              }
                              ;
                              if (module.can.show() && !module.is.active()) {
                                  module.debug('Showing dropdown');
                                  if (module.has.message() && !(module.has.maxSelections()
                                                                || module.has.allResultsFiltered())) {
                                      module.remove.message();
                                  }
                                  if (module.is.allFiltered()) {
                                      return true;
                                  }
                                  if (settings.onShow.call(element) !== false) {
                                      module.animate.show(function () {
                                          if (module.can.click()) {
                                              module.bind.intent();
                                          }
                                          if (module.has.menuSearch()) {
                                              module.focusSearch();
                                          }
                                          module.set.visible();
                                          callback.call(element);
                                      });
                                  }
                              }
                          },

                          hide: function (callback) {
                              callback = $.isFunction(callback)
                                  ? callback
                                  : function () {
                              }
                              ;
                              if (module.is.active()) {
                                  module.debug('Hiding dropdown');
                                  if (settings.onHide.call(element) !== false) {
                                      module.animate.hide(function () {
                                          module.remove.visible();
                                          callback.call(element);
                                      });
                                  }
                              }
                          },

                          hideOthers: function () {
                              module.verbose('Finding other dropdowns to hide');
                              $allModules
                                  .not($module)
                                  .has(selector.menu + '.' + className.visible)
                                  .dropdown('hide')
                              ;
                          },

                          hideMenu: function () {
                              module.verbose('Hiding menu  instantaneously');
                              module.remove.active();
                              module.remove.visible();
                              $menu.transition('hide');
                          },

                          hideSubMenus: function () {
                              var
                                  $subMenus = $menu.children(selector.item).find(selector.menu)
                                  ;
                              module.verbose('Hiding sub menus', $subMenus);
                              $subMenus.transition('hide');
                          },

                          bind: {
                              events: function () {
                                  if (hasTouch) {
                                      module.bind.touchEvents();
                                  }
                                  module.bind.keyboardEvents();
                                  module.bind.inputEvents();
                                  module.bind.mouseEvents();
                              },
                              touchEvents: function () {
                                  module.debug('Touch device detected binding additional touch events');
                                  if (module.is.searchSelection()) {
                                      // do nothing special yet
                                  }
                                  else if (module.is.single()) {
                                      $module
                                          .on('touchstart' + eventNamespace,
                                              module.event.test.toggle)
                                      ;
                                  }
                                  $menu
                                      .on('touchstart' + eventNamespace, selector.item,
                                          module.event.item.mouseenter)
                                  ;
                              },
                              keyboardEvents: function () {
                                  module.verbose('Binding keyboard events');
                                  $module
                                      .on('keydown' + eventNamespace, module.event.keydown)
                                  ;
                                  if (module.has.search()) {
                                      $module
                                          .on(module.get.inputEvent() + eventNamespace,
                                              selector.search,
                                              module.event.input)
                                      ;
                                  }
                                  if (module.is.multiple()) {
                                      $document
                                          .on('keydown' + elementNamespace,
                                              module.event.document.keydown)
                                      ;
                                  }
                              },
                              inputEvents: function () {
                                  module.verbose('Binding input change events');
                                  $module
                                      .on('change' + eventNamespace, selector.input,
                                          module.event.change)
                                  ;
                              },
                              mouseEvents: function () {
                                  module.verbose('Binding mouse events');
                                  if (module.is.multiple()) {
                                      $module
                                          .on('click' + eventNamespace, selector.label,
                                              module.event.label.click)
                                          .on('click' + eventNamespace, selector.remove,
                                              module.event.remove.click)
                                      ;
                                  }
                                  if (module.is.searchSelection()) {
                                      $module
                                          .on('mousedown' + eventNamespace, module.event.mousedown)
                                          .on('mouseup' + eventNamespace, module.event.mouseup)
                                          .on('mousedown' + eventNamespace, selector.menu,
                                              module.event.menu.mousedown)
                                          .on('mouseup' + eventNamespace, selector.menu,
                                              module.event.menu.mouseup)
                                          .on('click' + eventNamespace, selector.icon,
                                              module.event.icon.click)
                                          .on('focus' + eventNamespace, selector.search,
                                              module.event.search.focus)
                                          .on('click' + eventNamespace, selector.search,
                                              module.event.search.focus)
                                          .on('blur' + eventNamespace, selector.search,
                                              module.event.search.blur)
                                          .on('click' + eventNamespace, selector.text,
                                              module.event.text.focus)
                                      ;
                                      if (module.is.multiple()) {
                                          $module
                                              .on('click' + eventNamespace, module.event.click)
                                          ;
                                      }
                                  }
                                  else {
                                      if (settings.on == 'click') {
                                          $module
                                              .on('click' + eventNamespace, selector.icon,
                                                  module.event.icon.click)
                                              .on('click' + eventNamespace,
                                                  module.event.test.toggle)
                                          ;
                                      }
                                      else if (settings.on == 'hover') {
                                          $module
                                              .on('mouseenter' + eventNamespace, module.delay.show)
                                              .on('mouseleave' + eventNamespace, module.delay.hide)
                                          ;
                                      }
                                      else {
                                          $module
                                              .on(settings.on + eventNamespace, module.toggle)
                                          ;
                                      }
                                      $module
                                          .on('mousedown' + eventNamespace, module.event.mousedown)
                                          .on('mouseup' + eventNamespace, module.event.mouseup)
                                          .on('focus' + eventNamespace, module.event.focus)
                                          .on('blur' + eventNamespace, module.event.blur)
                                      ;
                                  }
                                  $menu
                                      .on('mouseenter' + eventNamespace, selector.item,
                                          module.event.item.mouseenter)
                                      .on('mouseleave' + eventNamespace, selector.item,
                                          module.event.item.mouseleave)
                                      .on('click' + eventNamespace, selector.item,
                                          module.event.item.click)
                                  ;
                              },
                              intent: function () {
                                  module.verbose('Binding hide intent event to document');
                                  if (hasTouch) {
                                      $document
                                          .on('touchstart' + elementNamespace,
                                              module.event.test.touch)
                                          .on('touchmove' + elementNamespace,
                                              module.event.test.touch)
                                      ;
                                  }
                                  $document
                                      .on('click' + elementNamespace, module.event.test.hide)
                                  ;
                              }
                          },

                          unbind: {
                              intent: function () {
                                  module.verbose('Removing hide intent event from document');
                                  if (hasTouch) {
                                      $document
                                          .off('touchstart' + elementNamespace)
                                          .off('touchmove' + elementNamespace)
                                      ;
                                  }
                                  $document
                                      .off('click' + elementNamespace)
                                  ;
                              }
                          },

                          filter: function (query) {
                              var
                                  searchTerm = (query !== undefined)
                                      ? query
                                      : module.get.query(),
                                  afterFiltered = function () {
                                      if (module.is.multiple()) {
                                          module.filterActive();
                                      }
                                      module.select.firstUnfiltered();
                                      if (module.has.allResultsFiltered()) {
                                          if (settings.onNoResults.call(element, searchTerm)) {
                                              if (settings.allowAdditions) {
                                                  if (settings.hideAdditions) {
                                                      module.verbose(
                                                          'User addition with no menu, setting empty style');
                                                      module.set.empty();
                                                      module.hideMenu();
                                                  }
                                              }
                                              else {
                                                  module.verbose('All items filtered, showing message',
                                                                 searchTerm);
                                                  module.add.message(message.noResults);
                                              }
                                          }
                                          else {
                                              module.verbose('All items filtered, hiding dropdown',
                                                             searchTerm);
                                              module.hideMenu();
                                          }
                                      }
                                      else {
                                          module.remove.empty();
                                          module.remove.message();
                                      }
                                      if (settings.allowAdditions) {
                                          module.add.userSuggestion(query);
                                      }
                                      if (module.is.searchSelection() && module.can.show()
                                          && module.is.focusedOnSearch()) {
                                          module.show();
                                      }
                                  }
                                  ;
                              //默认通过上次选择的内容进行筛选
                              searchTerm = searchTerm ? searchTerm : module.get.value();
                              if (settings.useLabels && module.has.maxSelections()) {
                                  return;
                              }
                              if (settings.apiSettings) {
                                  if (module.can.useAPI()) {
                                      module.queryRemote(searchTerm, function () {
                                          afterFiltered();
                                      });
                                  }
                                  else {
                                      module.error(error.noAPI);
                                  }
                              }
                              else {
                                  module.filterItems(searchTerm);
                                  afterFiltered();
                              }
                          },

                          queryRemote: function (query, callback) {
                              var
                                  apiSettings = {
                                      errorDuration: false,
                                      cache: 'local',
                                      throttle: settings.throttle,
                                      urlData: {
                                          query: query
                                      },
                                      onError: function () {
                                          module.add.message(message.serverError);
                                          callback();
                                      },
                                      onFailure: function () {
                                          module.add.message(message.serverError);
                                          callback();
                                      },
                                      onSuccess: function (response) {
                                          module.remove.message();
                                          module.setup.menu({
                                                                values: response[fields.remoteValues]
                                                            });
                                          callback();
                                      }
                                  }
                                  ;
                              if (!$module.api('get request')) {
                                  module.setup.api();
                              }
                              if (!settings.apiSettings.initCallBack) {
                                  if (settings.apiSettings.onSuccess) {
                                      var onSuccess = settings.apiSettings.onSuccess;
                                      settings.apiSettings.onSuccess =
                                      function (response, element) {
                                          onSuccess.call($module, response, element);
                                          callback();
                                      };
                                  }
                                  if (settings.apiSettings.onError) {
                                      var onError = settings.apiSettings.onError;
                                      settings.apiSettings.onError = function () {
                                          onError.call($module);
                                          callback();
                                      };
                                  }
                                  if (settings.apiSettings.onFailure) {
                                      var onFailure = settings.apiSettings.onFailure;
                                      settings.apiSettings.onFailure = function () {
                                          onFailure.call($module);
                                          callback();
                                      };
                                  }
                                  settings.apiSettings.initCallBack = true;
                              }
                              apiSettings = $.extend(true, {}, apiSettings, settings.apiSettings);
                              $module
                                  .api('setting', apiSettings)
                                  .api('query')
                              ;
                          },

                          filterItems: function (query) {
                              var
                                  searchTerm = (query !== undefined)
                                      ? query
                                      : module.get.query(),
                                  results = null,
                                  escapedTerm = module.escape.regExp(searchTerm),
                                  beginsWithRegExp = new RegExp(escapedTerm, 'igm')
                                  ;
                              // avoid loop if we're matching nothing
                              if (module.has.query()) {
                                  results = [];

                                  module.verbose('Searching for matching values', searchTerm);
                                  $item
                                      .each(function () {
                                                var
                                                    $choice = $(this),
                                                    text,
                                                    value
                                                    ;
                                                if (settings.match == 'both' || settings.match
                                                                                == 'text') {
                                                    text =
                                                    String(module.get.choiceText($choice, false));
                                                    if (text.search(beginsWithRegExp) !== -1) {
                                                        results.push(this);
                                                        return true;
                                                    }
                                                    else if (settings.fullTextSearch === 'exact'
                                                             && module.exactSearch(searchTerm,
                                                                                   text)) {
                                                        results.push(this);
                                                        return true;
                                                    }
                                                    else if (settings.fullTextSearch === true
                                                             && module.fuzzySearch(searchTerm,
                                                                                   text)) {
                                                        results.push(this);
                                                        return true;
                                                    }
                                                }
                                                if (settings.match == 'both' || settings.match
                                                                                == 'value') {
                                                    value =
                                                    String(module.get.choiceValue($choice, text));

                                                    if (value.search(beginsWithRegExp) !== -1) {
                                                        results.push(this);
                                                        return true;
                                                    }
                                                    else if (settings.fullTextSearch
                                                             && module.fuzzySearch(
                                                            searchTerm, value)) {
                                                        results.push(this);
                                                        return true;
                                                    }
                                                }
                                            })
                                  ;
                              }
                              module.debug('Showing only matched items', searchTerm);
                              module.remove.filteredItem();
                              if (results) {
                                  $item
                                      .not(results)
                                      .addClass(className.filtered)
                                  ;
                              }
                          },

                          fuzzySearch: function (query, term) {
                              var
                                  termLength = term.length,
                                  queryLength = query.length
                                  ;
                              query = query.toLowerCase();
                              term = term.toLowerCase();
                              if (queryLength > termLength) {
                                  return false;
                              }
                              if (queryLength === termLength) {
                                  return (query === term);
                              }
                              search: for (var characterIndex = 0, nextCharacterIndex = 0;
                                           characterIndex < queryLength; characterIndex++) {
                                  var
                                      queryCharacter = query.charCodeAt(characterIndex)
                                      ;
                                  while (nextCharacterIndex < termLength) {
                                      if (term.charCodeAt(nextCharacterIndex++)
                                          === queryCharacter) {
                                          continue search;
                                      }
                                  }
                                  return false;
                              }
                              return true;
                          },
                          exactSearch: function (query, term) {
                              query = query.toLowerCase();
                              term = term.toLowerCase();
                              if (term.indexOf(query) > -1) {
                                  return true;
                              }
                              return false;
                          },
                          filterActive: function () {
                              if (settings.useLabels) {
                                  $item.filter('.' + className.active)
                                      .addClass(className.filtered)
                                  ;
                              }
                          },

                          focusSearch: function (skipHandler) {
                              if (module.has.search() && !module.is.focusedOnSearch()) {
                                  if (skipHandler) {
                                      $module.off('focus' + eventNamespace, selector.search);
                                      $search.focus();
                                      $module.on('focus' + eventNamespace, selector.search,
                                                 module.event.search.focus);
                                  }
                                  else {
                                      $search.focus();
                                  }
                              }
                          },

                          forceSelection: function () {
                              var
                                  $currentlySelected = $item.not(className.filtered).filter(
                                      '.' + className.selected).eq(0),
                                  $activeItem = $item.not(className.filtered).filter(
                                      '.' + className.active).eq(0),
                                  $selectedItem = ($currentlySelected.length > 0)
                                      ? $currentlySelected
                                      : $activeItem,
                                  hasSelected = ($selectedItem.length > 0)
                                  ;
                              if (hasSelected) {
                                  module.debug('Forcing partial selection to selected item',
                                               $selectedItem);
                                  module.event.item.click.call($selectedItem, {}, true);
                                  return;
                              }
                              else {
                                  if (settings.allowAdditions) {
                                      module.set.selected(module.get.query());
                                      module.remove.searchTerm();
                                  }
                                  else {
                                      module.remove.searchTerm();
                                  }
                              }
                          },

                          event: {
                              change: function () {
                                  if (!internalChange) {
                                      module.debug('Input changed, updating selection');
                                      module.set.selected();
                                  }
                              },
                              focus: function () {
                                  if (settings.showOnFocus && !activated && module.is.hidden()
                                      && !pageLostFocus) {
                                      module.show();
                                  }
                              },
                              blur: function (event) {
                                  pageLostFocus = (document.activeElement === this);
                                  if (!activated && !pageLostFocus) {
                                      module.remove.activeLabel();
                                      module.hide();
                                  }
                              },
                              mousedown: function () {
                                  if (module.is.searchSelection()) {
                                      // prevent menu hiding on immediate re-focus
                                      willRefocus = true;
                                  }
                                  else {
                                      // prevents focus callback from occurring on mousedown
                                      activated = true;
                                  }
                              },
                              mouseup: function () {
                                  if (module.is.searchSelection()) {
                                      // prevent menu hiding on immediate re-focus
                                      willRefocus = false;
                                  }
                                  else {
                                      activated = false;
                                  }
                              },
                              click: function (event) {
                                  var
                                      $target = $(event.target)
                                      ;
                                  // focus search
                                  if ($target.is($module)) {
                                      if (!module.is.focusedOnSearch()) {
                                          module.focusSearch();
                                      }
                                      else {
                                          module.show();
                                      }
                                  }
                              },
                              search: {
                                  focus: function () {
                                      activated = true;
                                      if (module.is.multiple()) {
                                          module.remove.activeLabel();
                                      }
                                      if (settings.showOnFocus) {
                                          module.search();
                                      }
                                  },
                                  blur: function (event) {
                                      pageLostFocus = (document.activeElement === this);
                                      if (!willRefocus) {
                                          if (!itemActivated && !pageLostFocus) {
                                              if (settings.forceSelection) {
                                                  module.forceSelection();
                                              }
                                              module.hide();
                                          }
                                      }
                                      willRefocus = false;
                                  }
                              },
                              icon: {
                                  click: function (event) {
                                      module.toggle();
                                  }
                              },
                              text: {
                                  focus: function (event) {
                                      activated = true;
                                      module.focusSearch();
                                  }
                              },
                              input: function (event) {
                                  if (module.is.multiple() || module.is.searchSelection()) {
                                      module.set.filtered();
                                  }
                                  clearTimeout(module.timer);
                                  module.timer = setTimeout(module.search, settings.delay.search);
                              },
                              label: {
                                  click: function (event) {
                                      var
                                          $label = $(this),
                                          $labels = $module.find(selector.label),
                                          $activeLabels = $labels.filter('.' + className.active),
                                          $nextActive = $label.nextAll('.' + className.active),
                                          $prevActive = $label.prevAll('.' + className.active),
                                          $range = ($nextActive.length > 0)
                                              ? $label.nextUntil($nextActive).add($activeLabels).add(
                                              $label)
                                              : $label.prevUntil($prevActive).add($activeLabels).add(
                                              $label)
                                          ;
                                      if (event.shiftKey) {
                                          $activeLabels.removeClass(className.active);
                                          $range.addClass(className.active);
                                      }
                                      else if (event.ctrlKey) {
                                          $label.toggleClass(className.active);
                                      }
                                      else {
                                          $activeLabels.removeClass(className.active);
                                          $label.addClass(className.active);
                                      }
                                      settings.onLabelSelect.apply(this, $labels.filter(
                                          '.' + className.active));
                                  }
                              },
                              remove: {
                                  click: function () {
                                      var
                                          $label = $(this).parent()
                                          ;
                                      if ($label.hasClass(className.active)) {
                                          // remove all selected labels
                                          module.remove.activeLabels();
                                      }
                                      else {
                                          // remove this label only
                                          module.remove.activeLabels($label);
                                      }
                                  }
                              },
                              test: {
                                  toggle: function (event) {
                                      var
                                          toggleBehavior = (module.is.multiple())
                                              ? module.show
                                              : module.toggle
                                          ;
                                      if (module.is.bubbledLabelClick(event)
                                          || module.is.bubbledIconClick(event)) {
                                          return;
                                      }
                                      if (module.determine.eventOnElement(event, toggleBehavior)) {
                                          event.preventDefault();
                                      }
                                  },
                                  touch: function (event) {
                                      module.determine.eventOnElement(event, function () {
                                          if (event.type == 'touchstart') {
                                              module.timer = setTimeout(function () {
                                                  module.hide();
                                              }, settings.delay.touch);
                                          }
                                          else if (event.type == 'touchmove') {
                                              clearTimeout(module.timer);
                                          }
                                      });
                                      event.stopPropagation();
                                  },
                                  hide: function (event) {
                                      module.determine.eventInModule(event, module.hide);
                                  }
                              },
                              select: {
                                  mutation: function (mutations) {
                                      module.debug('<select> modified, recreating menu');
                                      module.setup.select();
                                  }
                              },
                              menu: {
                                  mutation: function (mutations) {
                                      var
                                          mutation = mutations[0],
                                          $addedNode = mutation.addedNodes
                                              ? $(mutation.addedNodes[0])
                                              : $(false),
                                          $removedNode = mutation.removedNodes
                                              ? $(mutation.removedNodes[0])
                                              : $(false),
                                          $changedNodes = $addedNode.add($removedNode),
                                          isUserAddition = $changedNodes.is(selector.addition)
                                                           || $changedNodes.closest(
                                                  selector.addition).length > 0,
                                          isMessage = $changedNodes.is(selector.message)
                                                      || $changedNodes.closest(selector.message).length
                                                         > 0
                                          ;
                                      if (isUserAddition || isMessage) {
                                          module.debug('Updating item selector cache');
                                          module.refreshItems();
                                      }
                                      else {
                                          module.debug('Menu modified, updating selector cache');
                                          module.refresh();
                                      }
                                  },
                                  mousedown: function () {
                                      itemActivated = true;
                                  },
                                  mouseup: function () {
                                      itemActivated = false;
                                  }
                              },
                              item: {
                                  mouseenter: function (event) {
                                      var
                                          $target = $(event.target),
                                          $item = $(this),
                                          $subMenu = $item.children(selector.menu),
                                          $otherMenus = $item.siblings(selector.item).children(
                                              selector.menu),
                                          hasSubMenu = ($subMenu.length > 0),
                                          isBubbledEvent = ($subMenu.find($target).length > 0)
                                          ;
                                      if (!isBubbledEvent && hasSubMenu) {
                                          clearTimeout(module.itemTimer);
                                          module.itemTimer = setTimeout(function () {
                                              module.verbose('Showing sub-menu', $subMenu);
                                              $.each($otherMenus, function () {
                                                  module.animate.hide(false, $(this));
                                              });
                                              module.animate.show(false, $subMenu);
                                          }, settings.delay.show);
                                          event.preventDefault();
                                      }
                                  },
                                  mouseleave: function (event) {
                                      var
                                          $subMenu = $(this).children(selector.menu)
                                          ;
                                      if ($subMenu.length > 0) {
                                          clearTimeout(module.itemTimer);
                                          module.itemTimer = setTimeout(function () {
                                              module.verbose('Hiding sub-menu', $subMenu);
                                              module.animate.hide(false, $subMenu);
                                          }, settings.delay.hide);
                                      }
                                  },
                                  click: function (event, skipRefocus) {
                                      var
                                          $choice = $(this),
                                          $target = (event)
                                              ? $(event.target)
                                              : $(''),
                                          $subMenu = $choice.find(selector.menu),
                                          text = module.get.choiceText($choice),
                                          value = module.get.choiceValue($choice, text),
                                          hasSubMenu = ($subMenu.length > 0),
                                          isBubbledEvent = ($subMenu.find($target).length > 0)
                                          ;
                                      if (!isBubbledEvent && (!hasSubMenu
                                                              || settings.allowCategorySelection)) {
                                          if (module.is.searchSelection()) {
                                              if (settings.allowAdditions) {
                                                  module.remove.userAddition();
                                              }
                                              module.remove.searchTerm();
                                              if (!module.is.focusedOnSearch() && !(skipRefocus
                                                                                    == true)) {
                                                  module.focusSearch(true);
                                              }
                                          }
                                          if (!settings.useLabels) {
                                              module.remove.filteredItem();
                                              module.set.scrollPosition($choice);
                                          }
                                          module.determine.selectAction.call(this, text, value);
                                      }
                                  }
                              },

                              document: {
                                  // label selection should occur even when element has no focus
                                  keydown: function (event) {
                                      var
                                          pressedKey = event.which,
                                          isShortcutKey = module.is.inObject(pressedKey, keys)
                                          ;
                                      if (isShortcutKey) {
                                          var
                                              $label = $module.find(selector.label),
                                              $activeLabel = $label.filter('.' + className.active),
                                              activeValue = $activeLabel.data(metadata.value),
                                              labelIndex = $label.index($activeLabel),
                                              labelCount = $label.length,
                                              hasActiveLabel = ($activeLabel.length > 0),
                                              hasMultipleActive = ($activeLabel.length > 1),
                                              isFirstLabel = (labelIndex === 0),
                                              isLastLabel = (labelIndex + 1 == labelCount),
                                              isSearch = module.is.searchSelection(),
                                              isFocusedOnSearch = module.is.focusedOnSearch(),
                                              isFocused = module.is.focused(),
                                              caretAtStart = (isFocusedOnSearch
                                                              && module.get.caretPosition() === 0),
                                              $nextLabel
                                              ;
                                          if (isSearch && !hasActiveLabel && !isFocusedOnSearch) {
                                              return;
                                          }

                                          if (pressedKey == keys.leftArrow) {
                                              // activate previous label
                                              if ((isFocused || caretAtStart) && !hasActiveLabel) {
                                                  module.verbose('Selecting previous label');
                                                  $label.last().addClass(className.active);
                                              }
                                              else if (hasActiveLabel) {
                                                  if (!event.shiftKey) {
                                                      module.verbose('Selecting previous label');
                                                      $label.removeClass(className.active);
                                                  }
                                                  else {
                                                      module.verbose(
                                                          'Adding previous label to selection');
                                                  }
                                                  if (isFirstLabel && !hasMultipleActive) {
                                                      $activeLabel.addClass(className.active);
                                                  }
                                                  else {
                                                      $activeLabel.prev(selector.siblingLabel)
                                                          .addClass(className.active)
                                                          .end()
                                                      ;
                                                  }
                                                  event.preventDefault();
                                              }
                                          }
                                          else if (pressedKey == keys.rightArrow) {
                                              // activate first label
                                              if (isFocused && !hasActiveLabel) {
                                                  $label.first().addClass(className.active);
                                              }
                                              // activate next label
                                              if (hasActiveLabel) {
                                                  if (!event.shiftKey) {
                                                      module.verbose('Selecting next label');
                                                      $label.removeClass(className.active);
                                                  }
                                                  else {
                                                      module.verbose('Adding next label to selection');
                                                  }
                                                  if (isLastLabel) {
                                                      if (isSearch) {
                                                          if (!isFocusedOnSearch) {
                                                              module.focusSearch();
                                                          }
                                                          else {
                                                              $label.removeClass(className.active);
                                                          }
                                                      }
                                                      else if (hasMultipleActive) {
                                                          $activeLabel.next(
                                                              selector.siblingLabel).addClass(
                                                              className.active);
                                                      }
                                                      else {
                                                          $activeLabel.addClass(className.active);
                                                      }
                                                  }
                                                  else {
                                                      $activeLabel.next(selector.siblingLabel).addClass(
                                                          className.active);
                                                  }
                                                  event.preventDefault();
                                              }
                                          }
                                          else if (pressedKey == keys.deleteKey || pressedKey
                                                                                   == keys.backspace) {
                                              if (hasActiveLabel) {
                                                  module.verbose('Removing active labels');
                                                  if (isLastLabel) {
                                                      if (isSearch && !isFocusedOnSearch) {
                                                          module.focusSearch();
                                                      }
                                                  }
                                                  $activeLabel.last().next(
                                                      selector.siblingLabel).addClass(className.active);
                                                  module.remove.activeLabels($activeLabel);
                                                  event.preventDefault();
                                              }
                                              else if (caretAtStart && !hasActiveLabel && pressedKey
                                                                                          == keys.backspace) {
                                                  module.verbose(
                                                      'Removing last label on input backspace');
                                                  $activeLabel =
                                                  $label.last().addClass(className.active);
                                                  module.remove.activeLabels($activeLabel);
                                              }
                                          }
                                          else {
                                              $activeLabel.removeClass(className.active);
                                          }
                                      }
                                  }
                              },

                              keydown: function (event) {
                                  var
                                      pressedKey = event.which,
                                      isShortcutKey = module.is.inObject(pressedKey, keys)
                                      ;
                                  if (isShortcutKey) {
                                      var
                                          $currentlySelected = $item.not(selector.unselectable).filter(
                                              '.' + className.selected).eq(0),
                                          $activeItem = $menu.children('.'
                                                                       + className.active).eq(0),
                                          $selectedItem = ($currentlySelected.length > 0)
                                              ? $currentlySelected
                                              : $activeItem,
                                          $visibleItems = ($selectedItem.length > 0)
                                              ? $selectedItem.siblings(
                                              ':not(.' + className.filtered + ')').addBack()
                                              : $menu.children(':not(.' + className.filtered + ')'),
                                          $subMenu = $selectedItem.children(selector.menu),
                                          $parentMenu = $selectedItem.closest(selector.menu),
                                          inVisibleMenu = ($parentMenu.hasClass(className.visible)
                                                           || $parentMenu.hasClass(className.animating)
                                                           || $parentMenu.parent(selector.menu).length
                                                              > 0),
                                          hasSubMenu = ($subMenu.length > 0),
                                          hasSelectedItem = ($selectedItem.length > 0),
                                          selectedIsSelectable = ($selectedItem.not(
                                              selector.unselectable).length > 0),
                                          delimiterPressed = (pressedKey == keys.delimiter
                                                              && settings.allowAdditions
                                                              && module.is.multiple()),
                                          isAdditionWithoutMenu = (settings.allowAdditions
                                                                   && settings.hideAdditions
                                                                   && (pressedKey == keys.enter
                                                                       || delimiterPressed)
                                                                   && selectedIsSelectable),
                                          $nextItem,
                                          isSubMenuItem,
                                          newIndex
                                          ;
                                      // allow selection with menu closed
                                      if (isAdditionWithoutMenu) {
                                          module.verbose('Selecting item from keyboard shortcut',
                                                         $selectedItem);
                                          module.event.item.click.call($selectedItem, event);
                                          if (module.is.searchSelection()) {
                                              module.remove.searchTerm();
                                          }
                                      }

                                      // visible menu keyboard shortcuts
                                      if (module.is.visible()) {

                                          // enter (select or open sub-menu)
                                          if (pressedKey == keys.enter || delimiterPressed) {
                                              if (pressedKey == keys.enter && hasSelectedItem
                                                  && hasSubMenu
                                                  && !settings.allowCategorySelection) {
                                                  module.verbose(
                                                      'Pressed enter on unselectable category, opening sub menu');
                                                  pressedKey = keys.rightArrow;
                                              }
                                              else if (selectedIsSelectable) {
                                                  module.verbose('Selecting item from keyboard shortcut',
                                                                 $selectedItem);
                                                  module.event.item.click.call($selectedItem,
                                                                               event);
                                                  if (module.is.searchSelection()) {
                                                      module.remove.searchTerm();
                                                  }
                                              }
                                              event.preventDefault();
                                          }

                                          // sub-menu actions
                                          if (hasSelectedItem) {

                                              if (pressedKey == keys.leftArrow) {

                                                  isSubMenuItem = ($parentMenu[0] !== $menu[0]);

                                                  if (isSubMenuItem) {
                                                      module.verbose(
                                                          'Left key pressed, closing sub-menu');
                                                      module.animate.hide(false, $parentMenu);
                                                      $selectedItem
                                                          .removeClass(className.selected)
                                                      ;
                                                      $parentMenu
                                                          .closest(selector.item)
                                                          .addClass(className.selected)
                                                      ;
                                                      event.preventDefault();
                                                  }
                                              }

                                              // right arrow (show sub-menu)
                                              if (pressedKey == keys.rightArrow) {
                                                  if (hasSubMenu) {
                                                      module.verbose(
                                                          'Right key pressed, opening sub-menu');
                                                      module.animate.show(false, $subMenu);
                                                      $selectedItem
                                                          .removeClass(className.selected)
                                                      ;
                                                      $subMenu
                                                          .find(selector.item).eq(0)
                                                          .addClass(className.selected)
                                                      ;
                                                      event.preventDefault();
                                                  }
                                              }
                                          }

                                          // up arrow (traverse menu up)
                                          if (pressedKey == keys.upArrow) {
                                              $nextItem = (hasSelectedItem && inVisibleMenu)
                                                  ? $selectedItem.prevAll(
                                                  selector.item + ':not(' + selector.unselectable
                                                  + ')').eq(0)
                                                  : $item.eq(0)
                                              ;
                                              if ($visibleItems.index($nextItem) < 0) {
                                                  module.verbose(
                                                      'Up key pressed but reached top of current menu');
                                                  event.preventDefault();
                                                  return;
                                              }
                                              else {
                                                  module.verbose('Up key pressed, changing active item');
                                                  $selectedItem
                                                      .removeClass(className.selected)
                                                  ;
                                                  $nextItem
                                                      .addClass(className.selected)
                                                  ;
                                                  module.set.scrollPosition($nextItem);
                                                  if (settings.selectOnKeydown
                                                      && module.is.single()) {
                                                      module.set.selectedItem($nextItem);
                                                  }
                                              }
                                              event.preventDefault();
                                          }

                                          // down arrow (traverse menu down)
                                          if (pressedKey == keys.downArrow) {
                                              $nextItem = (hasSelectedItem && inVisibleMenu)
                                                  ? $nextItem =
                                                    $selectedItem.nextAll(
                                                        selector.item + ':not('
                                                        + selector.unselectable + ')').eq(0)
                                                  : $item.eq(0)
                                              ;
                                              if ($nextItem.length === 0) {
                                                  module.verbose(
                                                      'Down key pressed but reached bottom of current menu');
                                                  event.preventDefault();
                                                  return;
                                              }
                                              else {
                                                  module.verbose(
                                                      'Down key pressed, changing active item');
                                                  $item
                                                      .removeClass(className.selected)
                                                  ;
                                                  $nextItem
                                                      .addClass(className.selected)
                                                  ;
                                                  module.set.scrollPosition($nextItem);
                                                  if (settings.selectOnKeydown
                                                      && module.is.single()) {
                                                      module.set.selectedItem($nextItem);
                                                  }
                                              }
                                              event.preventDefault();
                                          }

                                          // page down (show next page)
                                          if (pressedKey == keys.pageUp) {
                                              module.scrollPage('up');
                                              event.preventDefault();
                                          }
                                          if (pressedKey == keys.pageDown) {
                                              module.scrollPage('down');
                                              event.preventDefault();
                                          }

                                          // escape (close menu)
                                          if (pressedKey == keys.escape) {
                                              module.verbose('Escape key pressed, closing dropdown');
                                              module.hide();
                                          }

                                      }
                                      else {
                                          // delimiter key
                                          if (delimiterPressed) {
                                              event.preventDefault();
                                          }
                                          // down arrow (open menu)
                                          if (pressedKey == keys.downArrow
                                              && !module.is.visible()) {
                                              module.verbose('Down key pressed, showing dropdown');
                                              module.select.firstUnfiltered();
                                              module.show();
                                              event.preventDefault();
                                          }
                                      }
                                  }
                                  else {
                                      if (!module.has.search()) {
                                          module.set.selectedLetter(String.fromCharCode(pressedKey));
                                      }
                                  }
                              }
                          },

                          trigger: {
                              change: function () {
                                  var
                                      events = document.createEvent('HTMLEvents'),
                                      inputElement = $input[0]
                                      ;
                                  if (inputElement) {
                                      module.verbose('Triggering native change event');
                                      events.initEvent('change', true, false);
                                      inputElement.dispatchEvent(events);
                                  }
                              }
                          },

                          determine: {
                              selectAction: function (text, value) {
                                  module.verbose('Determining action', settings.action);
                                  if ($.isFunction(module.action[settings.action])) {
                                      module.verbose('Triggering preset action', settings.action,
                                                     text,
                                                     value);
                                      module.action[settings.action].call(element, text, value,
                                                                          this);
                                  }
                                  else if ($.isFunction(settings.action)) {
                                      module.verbose('Triggering user action', settings.action,
                                                     text,
                                                     value);
                                      settings.action.call(element, text, value, this);
                                  }
                                  else {
                                      module.error(error.action, settings.action);
                                  }
                              },
                              eventInModule: function (event, callback) {
                                  var
                                      $target = $(event.target),
                                      inDocument = ($target.closest(document.documentElement).length
                                                    > 0),
                                      inModule = ($target.closest($module).length > 0)
                                      ;
                                  callback = $.isFunction(callback)
                                      ? callback
                                      : function () {
                                  }
                                  ;
                                  if (inDocument && !inModule) {
                                      module.verbose('Triggering event', callback);
                                      callback();
                                      return true;
                                  }
                                  else {
                                      module.verbose('Event occurred in dropdown, canceling callback');
                                      return false;
                                  }
                              },
                              eventOnElement: function (event, callback) {
                                  var
                                      $target = $(event.target),
                                      $label = $target.closest(selector.siblingLabel),
                                      inVisibleDOM = document.body.contains(event.target),
                                      notOnLabel = ($module.find($label).length === 0),
                                      notInMenu = ($target.closest($menu).length === 0)
                                      ;
                                  callback = $.isFunction(callback)
                                      ? callback
                                      : function () {
                                  }
                                  ;
                                  if (inVisibleDOM && notOnLabel && notInMenu) {
                                      module.verbose('Triggering event', callback);
                                      callback();
                                      return true;
                                  }
                                  else {
                                      module.verbose(
                                          'Event occurred in dropdown menu, canceling callback');
                                      return false;
                                  }
                              }
                          },

                          action: {

                              nothing: function () {
                              },

                              activate: function (text, value, element) {
                                  value = (value !== undefined)
                                      ? value
                                      : text
                                  ;
                                  if (module.can.activate($(element))) {
                                      module.set.selected(value, $(element));
                                      if (module.is.multiple() && !module.is.allFiltered()) {
                                          return;
                                      }
                                      else {
                                          module.hideAndClear();
                                      }
                                  }
                              },

                              select: function (text, value, element) {
                                  value = (value !== undefined)
                                      ? value
                                      : text
                                  ;
                                  if (module.can.activate($(element))) {
                                      module.set.value(value, $(element));
                                      if (module.is.multiple() && !module.is.allFiltered()) {
                                          return;
                                      }
                                      else {
                                          module.hideAndClear();
                                      }
                                  }
                              },

                              combo: function (text, value, element) {
                                  value = (value !== undefined)
                                      ? value
                                      : text
                                  ;
                                  module.set.selected(value, $(element));
                                  module.hideAndClear();
                              },

                              hide: function (text, value, element) {
                                  module.set.value(value, text);
                                  module.hideAndClear();
                              }

                          },

                          get: {
                              setting: function () {
                                  return settings;
                              },
                              id: function () {
                                  return id;
                              },
                              defaultText: function () {
                                  return $module.data(metadata.defaultText);
                              },
                              defaultValue: function () {
                                  return $module.data(metadata.defaultValue);
                              },
                              placeholderText: function () {
                                  return $module.data(metadata.placeholderText) || '';
                              },
                              text: function () {
                                  return $text.text();
                              },
                              query: function () {
                                  return $.trim($search.val());
                              },
                              searchWidth: function (value) {
                                  value = (value !== undefined)
                                      ? value
                                      : $search.val()
                                  ;
                                  $sizer.text(value);
                                  // prevent rounding issues
                                  return Math.ceil($sizer.width() + 1);
                              },
                              selectionCount: function () {
                                  var
                                      values = module.get.values(),
                                      count
                                      ;
                                  count = ( module.is.multiple() )
                                      ? $.isArray(values)
                                              ? values.length
                                              : 0
                                      : (module.get.value() !== '')
                                              ? 1
                                              : 0
                                  ;
                                  return count;
                              },
                              transition: function ($subMenu) {
                                  return (settings.transition == 'auto')
                                      ? module.is.upward($subMenu)
                                             ? 'slide up'
                                             : 'slide down'
                                      : settings.transition
                                      ;
                              },
                              userValues: function () {
                                  var
                                      values = module.get.values()
                                      ;
                                  if (!values) {
                                      return false;
                                  }
                                  values = $.isArray(values)
                                      ? values
                                      : [values]
                                  ;
                                  return $.grep(values, function (value) {
                                      return (module.get.item(value) === false);
                                  });
                              },
                              uniqueArray: function (array) {
                                  return $.grep(array, function (value, index) {
                                      return $.inArray(value, array) === index;
                                  });
                              },
                              caretPosition: function () {
                                  var
                                      input = $search.get(0),
                                      range,
                                      rangeLength
                                      ;
                                  if ('selectionStart' in input) {
                                      return input.selectionStart;
                                  }
                                  else if (document.selection) {
                                      input.focus();
                                      range = document.selection.createRange();
                                      rangeLength = range.text.length;
                                      range.moveStart('character', -input.value.length);
                                      return range.text.length - rangeLength;
                                  }
                              },
                              value: function () {
                                  var
                                      value = ($input.length > 0)
                                          ? $input.val()
                                          : $module.data(metadata.value),
                                      isEmptyMultiselect = ($.isArray(value) && value.length === 1
                                                            && value[0] === '')
                                      ;
                                  // prevents placeholder element from being selected when multiple
                                  return (value === undefined || isEmptyMultiselect)
                                      ? ''
                                      : value
                                      ;
                              },
                              values: function () {
                                  var
                                      value = module.get.value()
                                      ;
                                  if (value === '') {
                                      return '';
                                  }
                                  return ( !module.has.selectInput() && module.is.multiple() )
                                      ? (typeof value == 'string') // delimited string
                                             ? value.split(settings.delimiter)
                                             : ''
                                      : value
                                      ;
                              },
                              remoteValues: function () {
                                  var
                                      values = module.get.values(),
                                      remoteValues = false
                                      ;
                                  if (values) {
                                      if (typeof values == 'string') {
                                          values = [values];
                                      }
                                      $.each(values, function (index, value) {
                                          var
                                              name = module.read.remoteData(value)
                                              ;
                                          module.verbose('Restoring value from session data', name,
                                                         value);
                                          if (name) {
                                              if (!remoteValues) {
                                                  remoteValues = {};
                                              }
                                              remoteValues[value] = name;
                                          }
                                      });
                                  }
                                  return remoteValues;
                              },
                              choiceText: function ($choice, preserveHTML) {
                                  preserveHTML = (preserveHTML !== undefined)
                                      ? preserveHTML
                                      : settings.preserveHTML
                                  ;
                                  if ($choice) {
                                      if ($choice.find(selector.menu).length > 0) {
                                          module.verbose('Retrieving text of element with sub-menu');
                                          $choice = $choice.clone();
                                          $choice.find(selector.menu).remove();
                                          $choice.find(selector.menuIcon).remove();
                                      }
                                      return ($choice.data(metadata.text) !== undefined)
                                          ? $choice.data(metadata.text)
                                          : (preserveHTML)
                                                 ? $.trim($choice.html())
                                                 : $.trim($choice.text())
                                          ;
                                  }
                              },
                              choiceValue: function ($choice, choiceText) {
                                  choiceText = choiceText || module.get.choiceText($choice);
                                  if (!$choice) {
                                      return false;
                                  }
                                  return ($choice.data(metadata.value) !== undefined)
                                      ? String($choice.data(metadata.value))
                                      : (typeof choiceText === 'string')
                                             ? $.trim(choiceText.toLowerCase())
                                             : String(choiceText)
                                      ;
                              },
                              inputEvent: function () {
                                  var
                                      input = $search[0]
                                      ;
                                  if (input) {
                                      return (input.oninput !== undefined)
                                          ? 'input'
                                          : (input.onpropertychange !== undefined)
                                                 ? 'propertychange'
                                                 : 'keyup'
                                          ;
                                  }
                                  return false;
                              },
                              selectValues: function () {
                                  var
                                      select = {}
                                      ;
                                  select.values = [];
                                  $module
                                      .find('option')
                                      .each(function () {
                                                var
                                                    $option = $(this),
                                                    name = $option.html(),
                                                    disabled = $option.attr('disabled'),
                                                    value = ( $option.attr('value') !== undefined )
                                                        ? $option.attr('value')
                                                        : name
                                                    ;
                                                if (settings.placeholder === 'auto' && value
                                                                                       === '') {
                                                    select.placeholder = name;
                                                }
                                                else {
                                                    select.values.push({
                                                                           name: name,
                                                                           value: value,
                                                                           disabled: disabled
                                                                       });
                                                }
                                            })
                                  ;
                                  if (settings.placeholder && settings.placeholder !== 'auto') {
                                      module.debug('Setting placeholder value to',
                                                   settings.placeholder);
                                      select.placeholder = settings.placeholder;
                                  }
                                  if (settings.sortSelect) {
                                      select.values.sort(function (a, b) {
                                          return (a.name > b.name)
                                              ? 1
                                              : -1
                                              ;
                                      });
                                      module.debug('Retrieved and sorted values from select',
                                                   select);
                                  }
                                  else {
                                      module.debug('Retrieved values from select', select);
                                  }
                                  return select;
                              },
                              activeItem: function () {
                                  return $item.filter('.' + className.active);
                              },
                              selectedItem: function () {
                                  var
                                      $selectedItem = $item.not(selector.unselectable).filter(
                                          '.' + className.selected)
                                      ;
                                  return ($selectedItem.length > 0)
                                      ? $selectedItem
                                      : $item.eq(0)
                                      ;
                              },
                              itemWithAdditions: function (value) {
                                  var
                                      $items = module.get.item(value),
                                      $userItems = module.create.userChoice(value),
                                      hasUserItems = ($userItems && $userItems.length > 0)
                                      ;
                                  if (hasUserItems) {
                                      $items = ($items.length > 0)
                                          ? $items.add($userItems)
                                          : $userItems
                                      ;
                                  }
                                  return $items;
                              },
                              item: function (value, strict) {
                                  var
                                      $selectedItem = false,
                                      shouldSearch,
                                      isMultiple
                                      ;
                                  value = (value !== undefined)
                                      ? value
                                      : ( module.get.values() !== undefined)
                                              ? module.get.values()
                                              : module.get.text()
                                  ;
                                  shouldSearch = (isMultiple)
                                      ? (value.length > 0)
                                      : (value !== undefined && value !== null)
                                  ;
                                  isMultiple = (module.is.multiple() && $.isArray(value));
                                  strict = (value === '' || value === 0)
                                      ? true
                                      : strict || false
                                  ;
                                  if (shouldSearch) {
                                      $item
                                          .each(function () {
                                                    var
                                                        $choice = $(this),
                                                        optionText = module.get.choiceText($choice),
                                                        optionValue = module.get.choiceValue($choice,
                                                                                             optionText)
                                                        ;
                                                    // safe early exit
                                                    if (optionValue === null || optionValue
                                                                                === undefined) {
                                                        return;
                                                    }
                                                    if (isMultiple) {
                                                        if ($.inArray(String(optionValue), value)
                                                            !== -1
                                                            || $.inArray(optionText, value)
                                                               !== -1) {
                                                            $selectedItem = ($selectedItem)
                                                                ? $selectedItem.add($choice)
                                                                : $choice
                                                            ;
                                                        }
                                                    }
                                                    else if (strict) {
                                                        module.verbose(
                                                            'Ambiguous dropdown value using strict type check',
                                                            $choice, value);
                                                        if (optionValue === value || optionText
                                                                                     === value) {
                                                            $selectedItem = $choice;
                                                            return true;
                                                        }
                                                    }
                                                    else {
                                                        if (String(optionValue) == String(value)
                                                            || optionText
                                                               == value) {
                                                            module.verbose('Found select item by value',
                                                                           optionValue, value);
                                                            $selectedItem = $choice;
                                                            return true;
                                                        }
                                                    }
                                                })
                                      ;
                                  }
                                  return $selectedItem;
                              }
                          },

                          check: {
                              maxSelections: function (selectionCount) {
                                  if (settings.maxSelections) {
                                      selectionCount = (selectionCount !== undefined)
                                          ? selectionCount
                                          : module.get.selectionCount()
                                      ;
                                      if (selectionCount >= settings.maxSelections) {
                                          module.debug('Maximum selection count reached');
                                          if (settings.useLabels) {
                                              $item.addClass(className.filtered);
                                              module.add.message(message.maxSelections);
                                          }
                                          return true;
                                      }
                                      else {
                                          module.verbose('No longer at maximum selection count');
                                          module.remove.message();
                                          module.remove.filteredItem();
                                          if (module.is.searchSelection()) {
                                              module.filterItems();
                                          }
                                          return false;
                                      }
                                  }
                                  return true;
                              }
                          },

                          restore: {
                              defaults: function () {
                                  module.clear();
                                  module.restore.defaultText();
                                  module.restore.defaultValue();
                              },
                              defaultText: function () {
                                  var
                                      defaultText = module.get.defaultText(),
                                      placeholderText = module.get.placeholderText
                                      ;
                                  if (defaultText === placeholderText) {
                                      module.debug('Restoring default placeholder text',
                                                   defaultText);
                                      module.set.placeholderText(defaultText);
                                  }
                                  else {
                                      module.debug('Restoring default text', defaultText);
                                      module.set.text(defaultText);
                                  }
                              },
                              placeholderText: function () {
                                  module.set.placeholderText();
                              },
                              defaultValue: function () {
                                  var
                                      defaultValue = module.get.defaultValue()
                                      ;
                                  if (defaultValue !== undefined) {
                                      module.debug('Restoring default value', defaultValue);
                                      if (defaultValue !== '') {
                                          module.set.value(defaultValue);
                                          module.set.selected();
                                      }
                                      else {
                                          module.remove.activeItem();
                                          module.remove.selectedItem();
                                      }
                                  }
                              },
                              labels: function () {
                                  if (settings.allowAdditions) {
                                      if (!settings.useLabels) {
                                          module.error(error.labels);
                                          settings.useLabels = true;
                                      }
                                      module.debug('Restoring selected values');
                                      module.create.userLabels();
                                  }
                                  module.check.maxSelections();
                              },
                              selected: function () {
                                  module.restore.values();
                                  if (module.is.multiple()) {
                                      module.debug('Restoring previously selected values and labels');
                                      module.restore.labels();
                                  }
                                  else {
                                      module.debug('Restoring previously selected values');
                                  }
                              },
                              values: function () {
                                  // prevents callbacks from occurring on initial load
                                  module.set.initialLoad();
                                  if (settings.apiSettings && settings.saveRemoteData
                                      && module.get.remoteValues()) {
                                      module.restore.remoteValues();
                                  }
                                  else {
                                      module.set.selected();
                                  }
                                  module.remove.initialLoad();
                              },
                              remoteValues: function () {
                                  var
                                      values = module.get.remoteValues()
                                      ;
                                  module.debug('Recreating selected from session data', values);
                                  if (values) {
                                      if (module.is.single()) {
                                          $.each(values, function (value, name) {
                                              module.set.text(name);
                                          });
                                      }
                                      else {
                                          $.each(values, function (value, name) {
                                              module.add.label(value, name);
                                          });
                                      }
                                  }
                              }
                          },

                          read: {
                              remoteData: function (value) {
                                  var
                                      name
                                      ;
                                  if (window.Storage === undefined) {
                                      module.error(error.noStorage);
                                      return;
                                  }
                                  name = sessionStorage.getItem(value);
                                  return (name !== undefined)
                                      ? name
                                      : false
                                      ;
                              }
                          },

                          save: {
                              defaults: function () {
                                  module.save.defaultText();
                                  module.save.placeholderText();
                                  module.save.defaultValue();
                              },
                              defaultValue: function () {
                                  var
                                      value = module.get.value()
                                      ;
                                  module.verbose('Saving default value as', value);
                                  $module.data(metadata.defaultValue, value);
                              },
                              defaultText: function () {
                                  var
                                      text = module.get.text()
                                      ;
                                  module.verbose('Saving default text as', text);
                                  $module.data(metadata.defaultText, text);
                              },
                              placeholderText: function () {
                                  var
                                      text
                                      ;
                                  if (settings.placeholder !== false && $text.hasClass(
                                          className.placeholder)) {
                                      text = module.get.text();
                                      module.verbose('Saving placeholder text as', text);
                                      $module.data(metadata.placeholderText, text);
                                  }
                              },
                              remoteData: function (name, value) {
                                  if (window.Storage === undefined) {
                                      module.error(error.noStorage);
                                      return;
                                  }
                                  module.verbose('Saving remote data to session storage', value,
                                                 name);
                                  sessionStorage.setItem(value, name);
                              }
                          },

                          clear: function () {
                              if (module.is.multiple() && settings.useLabels) {
                                  module.remove.labels();
                              }
                              else {
                                  module.remove.activeItem();
                                  module.remove.selectedItem();
                              }
                              module.set.placeholderText();
                              module.clearValue();
                          },

                          clearValue: function () {
                              module.set.value('');
                          },

                          scrollPage: function (direction, $selectedItem) {
                              var
                                  $currentItem = $selectedItem || module.get.selectedItem(),
                                  $menu = $currentItem.closest(selector.menu),
                                  menuHeight = $menu.outerHeight(),
                                  currentScroll = $menu.scrollTop(),
                                  itemHeight = $item.eq(0).outerHeight(),
                                  itemsPerPage = Math.floor(menuHeight / itemHeight),
                                  maxScroll = $menu.prop('scrollHeight'),
                                  newScroll = (direction == 'up')
                                      ? currentScroll - (itemHeight * itemsPerPage)
                                      : currentScroll + (itemHeight * itemsPerPage),
                                  $selectableItem = $item.not(selector.unselectable),
                                  isWithinRange,
                                  $nextSelectedItem,
                                  elementIndex
                                  ;
                              elementIndex = (direction == 'up')
                                  ? $selectableItem.index($currentItem) - itemsPerPage
                                  : $selectableItem.index($currentItem) + itemsPerPage
                              ;
                              isWithinRange = (direction == 'up')
                                  ? (elementIndex >= 0)
                                  : (elementIndex < $selectableItem.length)
                              ;
                              $nextSelectedItem = (isWithinRange)
                                  ? $selectableItem.eq(elementIndex)
                                  : (direction == 'up')
                                                      ? $selectableItem.first()
                                                      : $selectableItem.last()
                              ;
                              if ($nextSelectedItem.length > 0) {
                                  module.debug('Scrolling page', direction, $nextSelectedItem);
                                  $currentItem
                                      .removeClass(className.selected)
                                  ;
                                  $nextSelectedItem
                                      .addClass(className.selected)
                                  ;
                                  if (settings.selectOnKeydown && module.is.single()) {
                                      module.set.selectedItem($nextSelectedItem);
                                  }
                                  $menu
                                      .scrollTop(newScroll)
                                  ;
                              }
                          },

                          set: {
                              filtered: function () {
                                  var
                                      isMultiple = module.is.multiple(),
                                      isSearch = module.is.searchSelection(),
                                      isSearchMultiple = (isMultiple && isSearch),
                                      searchValue = (isSearch)
                                          ? module.get.query()
                                          : '',
                                      hasSearchValue = (typeof searchValue === 'string'
                                                        && searchValue.length > 0),
                                      searchWidth = module.get.searchWidth(),
                                      valueIsSet = searchValue !== ''
                                      ;
                                  if (isMultiple && hasSearchValue) {
                                      module.verbose('Adjusting input width', searchWidth,
                                                     settings.glyphWidth);
                                      $search.css('width', searchWidth);
                                  }
                                  if (hasSearchValue || (isSearchMultiple && valueIsSet)) {
                                      module.verbose('Hiding placeholder text');
                                      $text.addClass(className.filtered);
                                  }
                                  else if (!isMultiple || (isSearchMultiple && !valueIsSet)) {
                                      module.verbose('Showing placeholder text');
                                      $text.removeClass(className.filtered);
                                  }
                              },
                              empty: function () {
                                  $module.addClass(className.empty);
                              },
                              loading: function () {
                                  $module.addClass(className.loading);
                              },
                              placeholderText: function (text) {
                                  text = text || module.get.placeholderText();
                                  module.debug('Setting placeholder text', text);
                                  module.set.text(text);
                                  $text.addClass(className.placeholder);
                              },
                              tabbable: function () {
                                  if (module.has.search()) {
                                      module.debug('Added tabindex to searchable dropdown');
                                      $search
                                          .val('')
                                          .attr('tabindex', 0)
                                      ;
                                      $menu
                                          .attr('tabindex', -1)
                                      ;
                                  }
                                  else {
                                      module.debug('Added tabindex to dropdown');
                                      if ($module.attr('tabindex') === undefined) {
                                          $module
                                              .attr('tabindex', 0)
                                          ;
                                          $menu
                                              .attr('tabindex', -1)
                                          ;
                                      }
                                  }
                              },
                              initialLoad: function () {
                                  module.verbose('Setting initial load');
                                  initialLoad = true;
                              },
                              activeItem: function ($item) {
                                  if (settings.allowAdditions
                                      && $item.filter(selector.addition).length
                                         > 0) {
                                      $item.addClass(className.filtered);
                                  }
                                  else {
                                      $item.addClass(className.active);
                                  }
                              },
                              partialSearch: function (text) {
                                  var
                                      length = module.get.query().length
                                      ;
                                  $search.val(text.substr(0, length));
                              },
                              scrollPosition: function ($item, forceScroll) {
                                  var
                                      edgeTolerance = 5,
                                      $menu,
                                      hasActive,
                                      offset,
                                      itemHeight,
                                      itemOffset,
                                      menuOffset,
                                      menuScroll,
                                      menuHeight,
                                      abovePage,
                                      belowPage
                                      ;

                                  $item = $item || module.get.selectedItem();
                                  $menu = $item.closest(selector.menu);
                                  hasActive = ($item && $item.length > 0);
                                  forceScroll = (forceScroll !== undefined)
                                      ? forceScroll
                                      : false
                                  ;
                                  if ($item && $menu.length > 0 && hasActive) {
                                      itemOffset = $item.position().top;

                                      $menu.addClass(className.loading);
                                      menuScroll = $menu.scrollTop();
                                      menuOffset = $menu.offset().top;
                                      itemOffset = $item.offset().top;
                                      offset = menuScroll - menuOffset + itemOffset;
                                      if (!forceScroll) {
                                          menuHeight = $menu.height();
                                          belowPage =
                                          menuScroll + menuHeight < (offset + edgeTolerance);
                                          abovePage = ((offset - edgeTolerance) < menuScroll);
                                      }
                                      module.debug('Scrolling to active item', offset);
                                      if (forceScroll || abovePage || belowPage) {
                                          $menu.scrollTop(offset);
                                      }
                                      $menu.removeClass(className.loading);
                                  }
                              },
                              text: function (text) {
                                  if (settings.action !== 'select') {
                                      if (settings.action == 'combo') {
                                          module.debug('Changing combo button text', text, $combo);
                                          if (settings.preserveHTML) {
                                              $combo.html(text);
                                          }
                                          else {
                                              $combo.text(text);
                                          }
                                      }
                                      else {
                                          if (text !== module.get.placeholderText()) {
                                              $text.removeClass(className.placeholder);
                                          }
                                          module.debug('Changing text', text, $text);
                                          $text
                                              .removeClass(className.filtered)
                                          ;
                                          if (settings.preserveHTML) {
                                              $text.html(text);
                                          }
                                          else {
                                              $text.text(text);
                                          }
                                      }
                                  }
                              },
                              selectedItem: function ($item) {
                                  var
                                      value = module.get.choiceValue($item),
                                      text = module.get.choiceText($item, false)
                                      ;
                                  module.debug('Setting user selection to item', $item);
                                  module.remove.activeItem();
                                  module.set.partialSearch(text);
                                  module.set.activeItem($item);
                                  module.set.selected(value, $item);
                                  module.set.text(text);
                              },
                              selectedLetter: function (letter) {
                                  var
                                      $selectedItem = $item.filter('.' + className.selected),
                                      alreadySelectedLetter = $selectedItem.length > 0
                                                              && module.has.firstLetter($selectedItem,
                                                                                        letter),
                                      $nextValue = false,
                                      $nextItem
                                      ;
                                  // check next of same letter
                                  if (alreadySelectedLetter) {
                                      $nextItem = $selectedItem.nextAll($item).eq(0);
                                      if (module.has.firstLetter($nextItem, letter)) {
                                          $nextValue = $nextItem;
                                      }
                                  }
                                  // check all values
                                  if (!$nextValue) {
                                      $item
                                          .each(function () {
                                                    if (module.has.firstLetter($(this), letter)) {
                                                        $nextValue = $(this);
                                                        return false;
                                                    }
                                                })
                                      ;
                                  }
                                  // set next value
                                  if ($nextValue) {
                                      module.verbose('Scrolling to next value with letter', letter);
                                      module.set.scrollPosition($nextValue);
                                      $selectedItem.removeClass(className.selected);
                                      $nextValue.addClass(className.selected);
                                      if (settings.selectOnKeydown && module.is.single()) {
                                          module.set.selectedItem($nextValue);
                                      }
                                  }
                              },
                              direction: function ($menu) {
                                  if (settings.direction == 'auto') {
                                      if (module.is.onScreen($menu)) {
                                          module.remove.upward($menu);
                                      }
                                      else {
                                          module.set.upward($menu);
                                      }
                                  }
                                  else if (settings.direction == 'upward') {
                                      module.set.upward($menu);
                                  }
                              },
                              upward: function ($menu) {
                                  var $element = $menu || $module;
                                  $element.addClass(className.upward);
                              },
                              value: function (value, text, $selected) {
                                  var
                                      escapedValue = module.escape.value(value),
                                      hasInput = ($input.length > 0),
                                      isAddition = !module.has.value(value),
                                      currentValue = module.get.values(),
                                      stringValue = (value !== undefined)
                                          ? String(value)
                                          : value,
                                      newValue
                                      ;
                                  if (hasInput) {
                                      if (!settings.allowReselection && stringValue
                                                                        == currentValue) {
                                          module.verbose('Skipping value update already same value',
                                                         value, currentValue);
                                          if (!module.is.initialLoad()) {
                                              return;
                                          }
                                      }

                                      if (module.is.single() && module.has.selectInput()
                                          && module.can.extendSelect()) {
                                          module.debug('Adding user option', value);
                                          module.add.optionValue(value);
                                      }
                                      module.debug('Updating input value', escapedValue,
                                                   currentValue);
                                      internalChange = true;
                                      $input
                                          .val(escapedValue)
                                      ;
                                      if (settings.fireOnInit === false
                                          && module.is.initialLoad()) {
                                          module.debug(
                                              'Input native change event ignored on initial load');
                                      }
                                      else {
                                          module.trigger.change();
                                      }
                                      internalChange = false;
                                  }
                                  else {
                                      module.verbose('Storing value in metadata', escapedValue,
                                                     $input);
                                      if (escapedValue !== currentValue) {
                                          $module.data(metadata.value, stringValue);
                                      }
                                  }
                                  if (settings.fireOnInit === false && module.is.initialLoad()) {
                                      module.verbose('No callback on initial load',
                                                     settings.onChange);
                                  }
                                  else {
                                      settings.onChange.call(element, value, text, $selected);
                                  }
                              },
                              active: function () {
                                  $module
                                      .addClass(className.active)
                                  ;
                              },
                              multiple: function () {
                                  $module.addClass(className.multiple);
                              },
                              visible: function () {
                                  $module.addClass(className.visible);
                              },
                              exactly: function (value, $selectedItem) {
                                  module.debug('Setting selected to exact values');
                                  module.clear();
                                  module.set.selected(value, $selectedItem);
                              },
                              selected: function (value, $selectedItem) {
                                  var query = module.get.query();
                                  var
                                      isMultiple = module.is.multiple(),
                                      $userSelectedItem
                                      ;
                                  $selectedItem = (settings.allowAdditions)
                                      ? $selectedItem || module.get.itemWithAdditions(value)
                                      : $selectedItem || module.get.item(value)
                                  ;
                                  if (!$selectedItem) {
                                      return;
                                  }
                                  module.debug('Setting selected menu item to', $selectedItem);
                                  if (module.is.multiple()) {
                                      module.remove.searchWidth();
                                  }
                                  if (module.is.single()) {
                                      module.remove.activeItem();
                                      module.remove.selectedItem();
                                  }
                                  else if (settings.useLabels) {
                                      module.remove.selectedItem();
                                  }
                                  // select each item
                                  $selectedItem
                                      .each(function () {
                                                var
                                                    $selected = $(this),
                                                    selectedText = module.get.choiceText($selected),
                                                    selectedValue = module.get.choiceValue($selected,
                                                                                           selectedText),

                                                    isFiltered = $selected.hasClass(className.filtered),
                                                    isActive = $selected.hasClass(className.active),
                                                    isUserValue = $selected.hasClass(className.addition),
                                                    shouldAnimate = (isMultiple
                                                                     && $selectedItem.length == 1)
                                                    ;
                                                if (isMultiple) {
                                                    if (!isActive || isUserValue) {
                                                        if (settings.apiSettings
                                                            && settings.saveRemoteData) {
                                                            module.save.remoteData(selectedText,
                                                                                   selectedValue);
                                                        }
                                                        if (settings.useLabels) {
                                                            module.add.value(selectedValue,
                                                                             selectedText,
                                                                             $selected);
                                                            module.add.label(selectedValue,
                                                                             selectedText,
                                                                             shouldAnimate);
                                                            module.set.activeItem($selected);
                                                            module.filterActive();
                                                            module.select.nextAvailable($selectedItem);
                                                        }
                                                        else {
                                                            module.add.value(selectedValue,
                                                                             selectedText,
                                                                             $selected);
                                                            module.set.text(
                                                                module.add.variables(message.count));
                                                            module.set.activeItem($selected);
                                                        }
                                                    }
                                                    else if (!isFiltered) {
                                                        module.debug('Selected active value, removing label');
                                                        module.remove.selected(selectedValue);
                                                    }
                                                }
                                                else {
                                                    if (settings.apiSettings
                                                        && settings.saveRemoteData) {
                                                        module.save.remoteData(selectedText,
                                                                               selectedValue);
                                                    }
                                                    module.set.text(selectedText);
                                                    module.set.value(selectedValue, selectedText,
                                                                     $selected);
                                                    $selected.addClass(className.active).addClass(
                                                        className.selected);
                                                }
                                                //取得点击行的数据
                                                var rowData = $selectedItem.data();
                                                rowData.name = $selectedItem.text();
                                                var otherData = $selectedItem.attr("other-data");
                                                if (otherData) {
                                                    rowData.data =
                                                    $.parseJSON($.decodeUnicode(otherData));
                                                } else {
                                                    rowData.data = {};
                                                }
                                                //点击后回调
                                                rowData = $selectedItem.data();
                                                settings.afterSelected.call($module, rowData,
                                                                            selectedValue,
                                                                            selectedText);
                                            });
                              }
                          },

                          add: {
                              label: function (value, text, shouldAnimate) {
                                  var
                                      $next = module.is.searchSelection()
                                          ? $search
                                          : $text,
                                      escapedValue = module.escape.value(value),
                                      $label
                                      ;
                                  $label = $('<a />')
                                      .addClass(className.label)
                                      .attr('data-value', escapedValue)
                                      .html(templates.label(escapedValue, text))
                                  ;
                                  $label = settings.onLabelCreate.call($label, escapedValue, text);

                                  if (module.has.label(value)) {
                                      module.debug('Label already exists, skipping', escapedValue);
                                      return;
                                  }
                                  if (settings.label.variation) {
                                      $label.addClass(settings.label.variation);
                                  }
                                  if (shouldAnimate === true) {
                                      module.debug('Animating in label', $label);
                                      $label
                                          .addClass(className.hidden)
                                          .insertBefore($next)
                                          .transition(settings.label.transition,
                                                      settings.label.duration)
                                      ;
                                  }
                                  else {
                                      module.debug('Adding selection label', $label);
                                      $label
                                          .insertBefore($next)
                                      ;
                                  }
                              },
                              message: function (message) {
                                  var
                                      $message = $menu.children(selector.message),
                                      html = settings.templates.message(module.add.variables(message))
                                      ;
                                  if ($message.length > 0) {
                                      $message
                                          .html(html)
                                      ;
                                  }
                                  else {
                                      $message = $('<div/>')
                                          .html(html)
                                          .addClass(className.message)
                                          .appendTo($menu)
                                      ;
                                  }
                              },
                              optionValue: function (value) {
                                  var
                                      escapedValue = module.escape.value(value),
                                      $option = $input.find('option[value="' + escapedValue + '"]'),
                                      hasOption = ($option.length > 0)
                                      ;
                                  if (hasOption) {
                                      return;
                                  }
                                  // temporarily disconnect observer
                                  module.disconnect.selectObserver();
                                  if (module.is.single()) {
                                      module.verbose('Removing previous user addition');
                                      $input.find('option.' + className.addition).remove();
                                  }
                                  $('<option/>')
                                      .prop('value', escapedValue)
                                      .addClass(className.addition)
                                      .html(value)
                                      .appendTo($input)
                                  ;
                                  module.verbose('Adding user addition as an <option>', value);
                                  module.observe.select();
                              },
                              userSuggestion: function (value) {
                                  var
                                      $addition = $menu.children(selector.addition),
                                      $existingItem = module.get.item(value),
                                      alreadyHasValue = $existingItem && $existingItem.not(
                                              selector.addition).length,
                                      hasUserSuggestion = $addition.length > 0,
                                      html
                                      ;
                                  if (settings.useLabels && module.has.maxSelections()) {
                                      return;
                                  }
                                  if (value === '' || alreadyHasValue) {
                                      $addition.remove();
                                      return;
                                  }
                                  if (hasUserSuggestion) {
                                      $addition
                                          .data(metadata.value, value)
                                          .data(metadata.text, value)
                                          .attr('data-' + metadata.value, value)
                                          .attr('data-' + metadata.text, value)
                                          .removeClass(className.filtered)
                                      ;
                                      if (!settings.hideAdditions) {
                                          html =
                                          settings.templates.addition(
                                              module.add.variables(message.addResult, value));
                                          $addition
                                              .html(html)
                                          ;
                                      }
                                      module.verbose('Replacing user suggestion with new value',
                                                     $addition);
                                  }
                                  else {
                                      $addition = module.create.userChoice(value);
                                      $addition
                                          .prependTo($menu)
                                      ;
                                      module.verbose(
                                          'Adding item choice to menu corresponding with user choice addition',
                                          $addition);
                                  }
                                  if (!settings.hideAdditions || module.is.allFiltered()) {
                                      $addition
                                          .addClass(className.selected)
                                          .siblings()
                                          .removeClass(className.selected)
                                      ;
                                  }
                                  module.refreshItems();
                              },
                              variables: function (message, term) {
                                  var
                                      hasCount = (message.search('{count}') !== -1),
                                      hasMaxCount = (message.search('{maxCount}') !== -1),
                                      hasTerm = (message.search('{term}') !== -1),
                                      values,
                                      count,
                                      query
                                      ;
                                  module.verbose('Adding templated variables to message', message);
                                  if (hasCount) {
                                      count = module.get.selectionCount();
                                      message = message.replace('{count}', count);
                                  }
                                  if (hasMaxCount) {
                                      count = module.get.selectionCount();
                                      message =
                                      message.replace('{maxCount}', settings.maxSelections);
                                  }
                                  if (hasTerm) {
                                      query = term || module.get.query();
                                      message = message.replace('{term}', query);
                                  }
                                  return message;
                              },
                              value: function (addedValue, addedText, $selectedItem) {
                                  var
                                      currentValue = module.get.values(),
                                      newValue
                                      ;
                                  if (addedValue === '') {
                                      module.debug('Cannot select blank values from multiselect');
                                      return;
                                  }
                                  // extend current array
                                  if ($.isArray(currentValue)) {
                                      newValue = currentValue.concat([addedValue]);
                                      newValue = module.get.uniqueArray(newValue);
                                  }
                                  else {
                                      newValue = [addedValue];
                                  }
                                  // add values
                                  if (module.has.selectInput()) {
                                      if (module.can.extendSelect()) {
                                          module.debug('Adding value to select', addedValue,
                                                       newValue,
                                                       $input);
                                          module.add.optionValue(addedValue);
                                      }
                                  }
                                  else {
                                      newValue = newValue.join(settings.delimiter);
                                      module.debug('Setting hidden input to delimited value',
                                                   newValue,
                                                   $input);
                                  }

                                  if (settings.fireOnInit === false && module.is.initialLoad()) {
                                      module.verbose('Skipping onadd callback on initial load',
                                                     settings.onAdd);
                                  }
                                  else {
                                      settings.onAdd.call(element, addedValue, addedText,
                                                          $selectedItem);
                                  }
                                  module.set.value(newValue, addedValue, addedText, $selectedItem);
                                  module.check.maxSelections();
                              }
                          },

                          remove: {
                              active: function () {
                                  $module.removeClass(className.active);
                              },
                              activeLabel: function () {
                                  $module.find(selector.label).removeClass(className.active);
                              },
                              empty: function () {
                                  $module.removeClass(className.empty);
                              },
                              loading: function () {
                                  $module.removeClass(className.loading);
                              },
                              initialLoad: function () {
                                  initialLoad = false;
                              },
                              upward: function ($menu) {
                                  var $element = $menu || $module;
                                  $element.removeClass(className.upward);
                              },
                              visible: function () {
                                  $module.removeClass(className.visible);
                              },
                              activeItem: function () {
                                  $item.removeClass(className.active);
                              },
                              filteredItem: function () {
                                  if (settings.useLabels && module.has.maxSelections()) {
                                      return;
                                  }
                                  if (settings.useLabels && module.is.multiple()) {
                                      $item.not('.'
                                                + className.active).removeClass(className.filtered);
                                  }
                                  else {
                                      $item.removeClass(className.filtered);
                                  }
                                  module.remove.empty();
                              },
                              optionValue: function (value) {
                                  var
                                      escapedValue = module.escape.value(value),
                                      $option = $input.find('option[value="' + escapedValue + '"]'),
                                      hasOption = ($option.length > 0)
                                      ;
                                  if (!hasOption || !$option.hasClass(className.addition)) {
                                      return;
                                  }
                                  // temporarily disconnect observer
                                  if (selectObserver) {
                                      selectObserver.disconnect();
                                      module.verbose('Temporarily disconnecting mutation observer');
                                  }
                                  $option.remove();
                                  module.verbose('Removing user addition as an <option>',
                                                 escapedValue);
                                  if (selectObserver) {
                                      selectObserver.observe($input[0], {
                                          childList: true,
                                          subtree: true
                                      });
                                  }
                              },
                              message: function () {
                                  $menu.children(selector.message).remove();
                              },
                              searchWidth: function () {
                                  $search.css('width', '');
                              },
                              searchTerm: function () {
                                  module.verbose('Cleared search term');
                                  $search.val('');
                                  module.set.filtered();
                              },
                              userAddition: function () {
                                  $item.filter(selector.addition).remove();
                              },
                              selected: function (value, $selectedItem) {
                                  $selectedItem = (settings.allowAdditions)
                                      ? $selectedItem || module.get.itemWithAdditions(value)
                                      : $selectedItem || module.get.item(value)
                                  ;

                                  if (!$selectedItem) {
                                      return false;
                                  }

                                  $selectedItem
                                      .each(function () {
                                                var
                                                    $selected = $(this),
                                                    selectedText = module.get.choiceText($selected),
                                                    selectedValue = module.get.choiceValue($selected,
                                                                                           selectedText)
                                                    ;
                                                if (module.is.multiple()) {
                                                    if (settings.useLabels) {
                                                        module.remove.value(selectedValue,
                                                                            selectedText,
                                                                            $selected);
                                                        module.remove.label(selectedValue);
                                                    }
                                                    else {
                                                        module.remove.value(selectedValue,
                                                                            selectedText,
                                                                            $selected);
                                                        if (module.get.selectionCount() === 0) {
                                                            module.set.placeholderText();
                                                        }
                                                        else {
                                                            module.set.text(
                                                                module.add.variables(message.count));
                                                        }
                                                    }
                                                }
                                                else {
                                                    module.remove.value(selectedValue, selectedText,
                                                                        $selected);
                                                }
                                                $selected
                                                    .removeClass(className.filtered)
                                                    .removeClass(className.active)
                                                ;
                                                if (settings.useLabels) {
                                                    $selected.removeClass(className.selected);
                                                }
                                            })
                                  ;
                              },
                              selectedItem: function () {
                                  $item.removeClass(className.selected);
                              },
                              value: function (removedValue, removedText, $removedItem) {
                                  var
                                      values = module.get.values(),
                                      newValue
                                      ;
                                  if (module.has.selectInput()) {
                                      module.verbose('Input is <select> removing selected option',
                                                     removedValue);
                                      newValue = module.remove.arrayValue(removedValue, values);
                                      module.remove.optionValue(removedValue);
                                  }
                                  else {
                                      module.verbose('Removing from delimited values',
                                                     removedValue);
                                      newValue = module.remove.arrayValue(removedValue, values);
                                      newValue = newValue.join(settings.delimiter);
                                  }
                                  if (settings.fireOnInit === false && module.is.initialLoad()) {
                                      module.verbose('No callback on initial load',
                                                     settings.onRemove);
                                  }
                                  else {
                                      settings.onRemove.call(element, removedValue, removedText,
                                                             $removedItem);
                                  }
                                  module.set.value(newValue, removedText, $removedItem);
                                  module.check.maxSelections();
                              },
                              arrayValue: function (removedValue, values) {
                                  if (!$.isArray(values)) {
                                      values = [values];
                                  }
                                  values = $.grep(values, function (value) {
                                      return (removedValue != value);
                                  });
                                  module.verbose('Removed value from delimited string',
                                                 removedValue,
                                                 values);
                                  return values;
                              },
                              label: function (value, shouldAnimate) {
                                  var
                                      $labels = $module.find(selector.label),
                                      $removedLabel = $labels.filter('[data-value="' + value + '"]')
                                      ;
                                  module.verbose('Removing label', $removedLabel);
                                  $removedLabel.remove();
                              },
                              activeLabels: function ($activeLabels) {
                                  $activeLabels =
                                  $activeLabels || $module.find(selector.label).filter(
                                      '.' + className.active);
                                  module.verbose('Removing active label selections', $activeLabels);
                                  module.remove.labels($activeLabels);
                              },
                              labels: function ($labels) {
                                  $labels = $labels || $module.find(selector.label);
                                  module.verbose('Removing labels', $labels);
                                  $labels
                                      .each(function () {
                                                var
                                                    $label = $(this),
                                                    value = $label.data(metadata.value),
                                                    stringValue = (value !== undefined)
                                                        ? String(value)
                                                        : value,
                                                    isUserValue = module.is.userValue(stringValue)
                                                    ;
                                                if (settings.onLabelRemove.call($label, value)
                                                    === false) {
                                                    module.debug('Label remove callback cancelled removal');
                                                    return;
                                                }
                                                module.remove.message();
                                                if (isUserValue) {
                                                    module.remove.value(stringValue);
                                                    module.remove.label(stringValue);
                                                }
                                                else {
                                                    // selected will also remove label
                                                    module.remove.selected(stringValue);
                                                }
                                            })
                                  ;
                              },
                              tabbable: function () {
                                  if (module.has.search()) {
                                      module.debug('Searchable dropdown initialized');
                                      $search
                                          .removeAttr('tabindex')
                                      ;
                                      $menu
                                          .removeAttr('tabindex')
                                      ;
                                  }
                                  else {
                                      module.debug('Simple selection dropdown initialized');
                                      $module
                                          .removeAttr('tabindex')
                                      ;
                                      $menu
                                          .removeAttr('tabindex')
                                      ;
                                  }
                              }
                          },

                          has: {
                              menuSearch: function () {
                                  return (module.has.search() && $search.closest($menu).length > 0);
                              },
                              search: function () {
                                  return ($search.length > 0);
                              },
                              sizer: function () {
                                  return ($sizer.length > 0);
                              },
                              selectInput: function () {
                                  return ( $input.is('select') );
                              },
                              minCharacters: function (searchTerm) {
                                  if (settings.minCharacters) {
                                      searchTerm = (searchTerm !== undefined)
                                          ? String(searchTerm)
                                          : String(module.get.query())
                                      ;
                                      return (searchTerm.length >= settings.minCharacters);
                                  }
                                  return true;
                              },
                              firstLetter: function ($item, letter) {
                                  var
                                      text,
                                      firstLetter
                                      ;
                                  if (!$item || $item.length === 0 || typeof letter !== 'string') {
                                      return false;
                                  }
                                  text = module.get.choiceText($item, false);
                                  letter = letter.toLowerCase();
                                  firstLetter = String(text).charAt(0).toLowerCase();
                                  return (letter == firstLetter);
                              },
                              input: function () {
                                  return ($input.length > 0);
                              },
                              items: function () {
                                  return ($item.length > 0);
                              },
                              menu: function () {
                                  return ($menu.length > 0);
                              },
                              message: function () {
                                  return ($menu.children(selector.message).length !== 0);
                              },
                              label: function (value) {
                                  var
                                      escapedValue = module.escape.value(value),
                                      $labels = $module.find(selector.label)
                                      ;
                                  return ($labels.filter('[data-value="' + escapedValue
                                                         + '"]').length
                                          > 0);
                              },
                              maxSelections: function () {
                                  return (settings.maxSelections && module.get.selectionCount()
                                                                    >= settings.maxSelections);
                              },
                              allResultsFiltered: function () {
                                  var
                                      $normalResults = $item.not(selector.addition)
                                      ;
                                  return ($normalResults.filter(selector.unselectable).length
                                          === $normalResults.length);
                              },
                              userSuggestion: function () {
                                  return ($menu.children(selector.addition).length > 0);
                              },
                              query: function () {
                                  return (module.get.query() !== '');
                              },
                              value: function (value) {
                                  var
                                      values = module.get.values(),
                                      hasValue = $.isArray(values)
                                          ? values && ($.inArray(value, values) !== -1)
                                          : (values == value)
                                      ;
                                  return (hasValue)
                                      ? true
                                      : false
                                      ;
                              }
                          },

                          is: {
                              active: function () {
                                  return $module.hasClass(className.active);
                              },
                              bubbledLabelClick: function (event) {
                                  return $(event.target).is('select, input') && $module.closest(
                                          'label').length > 0;
                              },
                              bubbledIconClick: function (event) {
                                  return $(event.target).closest($icon).length > 0;
                              },
                              alreadySetup: function () {
                                  return ($module.is('select')
                                          && $module.parent(selector.dropdown).length
                                             > 0 && $module.prev().length === 0);
                              },
                              animating: function ($subMenu) {
                                  return ($subMenu)
                                      ? $subMenu.transition && $subMenu.transition('is animating')
                                      : $menu.transition && $menu.transition('is animating')
                                      ;
                              },
                              disabled: function () {
                                  return $module.hasClass(className.disabled);
                              },
                              focused: function () {
                                  return (document.activeElement === $module[0]);
                              },
                              focusedOnSearch: function () {
                                  return (document.activeElement === $search[0]);
                              },
                              allFiltered: function () {
                                  return ( (module.is.multiple() || module.has.search())
                                           && !(settings.hideAdditions == false
                                                && module.has.userSuggestion())
                                           && !module.has.message()
                                           && module.has.allResultsFiltered() );
                              },
                              hidden: function ($subMenu) {
                                  return !module.is.visible($subMenu);
                              },
                              initialLoad: function () {
                                  return initialLoad;
                              },
                              onScreen: function ($subMenu) {
                                  var
                                      $currentMenu = $subMenu || $menu,
                                      canOpenDownward = true,
                                      onScreen = {},
                                      calculations
                                      ;
                                  $currentMenu.addClass(className.loading);
                                  calculations = {
                                      context: {
                                          scrollTop: $context.scrollTop(),
                                          height: $context.outerHeight()
                                      },
                                      menu: {
                                          offset: $currentMenu.offset(),
                                          height: $currentMenu.outerHeight()
                                      }
                                  };
                                  onScreen = {
                                      above: (calculations.context.scrollTop)
                                             <= calculations.menu.offset.top
                                                - calculations.menu.height,
                                      below: (calculations.context.scrollTop
                                              + calculations.context.height)
                                             >= calculations.menu.offset.top
                                                + calculations.menu.height
                                  };
                                  if (onScreen.below) {
                                      module.verbose('Dropdown can fit in context downward',
                                                     onScreen);
                                      canOpenDownward = true;
                                  }
                                  else if (!onScreen.below && !onScreen.above) {
                                      module.verbose(
                                          'Dropdown cannot fit in either direction, favoring downward',
                                          onScreen);
                                      canOpenDownward = true;
                                  }
                                  else {
                                      module.verbose('Dropdown cannot fit below, opening upward',
                                                     onScreen);
                                      canOpenDownward = false;
                                  }
                                  $currentMenu.removeClass(className.loading);
                                  return canOpenDownward;
                              },
                              inObject: function (needle, object) {
                                  var
                                      found = false
                                      ;
                                  $.each(object, function (index, property) {
                                      if (property == needle) {
                                          found = true;
                                          return true;
                                      }
                                  });
                                  return found;
                              },
                              multiple: function () {
                                  return $module.hasClass(className.multiple);
                              },
                              single: function () {
                                  return !module.is.multiple();
                              },
                              selectMutation: function (mutations) {
                                  var
                                      selectChanged = false
                                      ;
                                  $.each(mutations, function (index, mutation) {
                                      if (mutation.target && $(mutation.target).is('select')) {
                                          selectChanged = true;
                                          return true;
                                      }
                                  });
                                  return selectChanged;
                              },
                              search: function () {
                                  return $module.hasClass(className.search);
                              },
                              searchSelection: function () {
                                  return ( module.has.search()
                                           && $search.parent(selector.dropdown).length
                                              === 1 );
                              },
                              selection: function () {
                                  return $module.hasClass(className.selection);
                              },
                              userValue: function (value) {
                                  return ($.inArray(value, module.get.userValues()) !== -1);
                              },
                              upward: function ($menu) {
                                  var $element = $menu || $module;
                                  return $element.hasClass(className.upward);
                              },
                              visible: function ($subMenu) {
                                  return ($subMenu)
                                      ? $subMenu.hasClass(className.visible)
                                      : $menu.hasClass(className.visible)
                                      ;
                              }
                          },

                          can: {
                              activate: function ($item) {
                                  if (settings.useLabels) {
                                      return true;
                                  }
                                  if (!module.has.maxSelections()) {
                                      return true;
                                  }
                                  if (module.has.maxSelections()
                                      && $item.hasClass(className.active)) {
                                      return true;
                                  }
                                  return false;
                              },
                              click: function () {
                                  return (hasTouch || settings.on == 'click');
                              },
                              extendSelect: function () {
                                  return settings.allowAdditions || settings.apiSettings;
                              },
                              show: function () {
                                  return !module.is.disabled() && (module.has.items()
                                                                   || module.has.message());
                              },
                              useAPI: function () {
                                  return $.fn.api !== undefined;
                              }
                          },

                          animate: {
                              show: function (callback, $subMenu) {
                                  var
                                      $currentMenu = $subMenu || $menu,
                                      start = ($subMenu)
                                          ? function () {
                                      }
                                          : function () {
                                          module.hideSubMenus();
                                          module.hideOthers();
                                          module.set.active();
                                      },
                                      transition
                                      ;
                                  callback = $.isFunction(callback)
                                      ? callback
                                      : function () {
                                  }
                                  ;
                                  module.verbose('Doing menu show animation', $currentMenu);
                                  module.set.direction($subMenu);
                                  transition = module.get.transition($subMenu);
                                  if (module.is.selection()) {
                                      module.set.scrollPosition(module.get.selectedItem(), true);
                                  }
                                  if (module.is.hidden($currentMenu) || module.is.animating(
                                          $currentMenu)) {
                                      if (transition == 'none') {
                                          start();
                                          $currentMenu.transition('show');
                                          callback.call(element);
                                      }
                                      else if ($.fn.transition !== undefined && $module.transition(
                                              'is supported')) {
                                          $currentMenu
                                              .transition({
                                                              animation: transition + ' in',
                                                              debug: settings.debug,
                                                              verbose: settings.verbose,
                                                              duration: settings.duration,
                                                              queue: true,
                                                              onStart: start,
                                                              onComplete: function () {
                                                                  callback.call(element);
                                                              }
                                                          })
                                          ;
                                      }
                                      else {
                                          module.error(error.noTransition, transition);
                                      }
                                  }
                              },
                              hide: function (callback, $subMenu) {
                                  var
                                      $currentMenu = $subMenu || $menu,
                                      duration = ($subMenu)
                                          ? (settings.duration * 0.9)
                                          : settings.duration,
                                      start = ($subMenu)
                                          ? function () {
                                      }
                                          : function () {
                                          if (module.can.click()) {
                                              module.unbind.intent();
                                          }
                                          module.remove.active();
                                      },
                                      transition = module.get.transition($subMenu)
                                      ;
                                  callback = $.isFunction(callback)
                                      ? callback
                                      : function () {
                                  }
                                  ;
                                  if (module.is.visible($currentMenu) || module.is.animating(
                                          $currentMenu)) {
                                      module.verbose('Doing menu hide animation', $currentMenu);

                                      if (transition == 'none') {
                                          start();
                                          $currentMenu.transition('hide');
                                          callback.call(element);
                                      }
                                      else if ($.fn.transition !== undefined && $module.transition(
                                              'is supported')) {
                                          $currentMenu
                                              .transition({
                                                              animation: transition + ' out',
                                                              duration: settings.duration,
                                                              debug: settings.debug,
                                                              verbose: settings.verbose,
                                                              queue: true,
                                                              onStart: start,
                                                              onComplete: function () {
                                                                  if (settings.direction
                                                                      == 'auto') {
                                                                      module.remove.upward($subMenu);
                                                                  }
                                                                  callback.call(element);
                                                              }
                                                          })
                                          ;
                                      }
                                      else {
                                          module.error(error.transition);
                                      }
                                  }
                              }
                          },

                          hideAndClear: function () {
                              module.remove.searchTerm();
                              if (module.has.maxSelections()) {
                                  return;
                              }
                              if (module.has.search()) {
                                  module.hide(function () {
                                      module.remove.filteredItem();
                                  });
                              }
                              else {
                                  module.hide();
                              }
                          },

                          delay: {
                              show: function () {
                                  module.verbose('Delaying show event to ensure user intent');
                                  clearTimeout(module.timer);
                                  module.timer = setTimeout(module.show, settings.delay.show);
                              },
                              hide: function () {
                                  module.verbose('Delaying hide event to ensure user intent');
                                  clearTimeout(module.timer);
                                  module.timer = setTimeout(module.hide, settings.delay.hide);
                              }
                          },

                          escape: {
                              value: function (value) {
                                  var
                                      multipleValues = $.isArray(value),
                                      stringValue = (typeof value === 'string'),
                                      isUnparsable = (!stringValue && !multipleValues),
                                      hasQuotes = (stringValue && value.search(regExp.quote)
                                                                  !== -1),
                                      values = []
                                      ;
                                  if (!module.has.selectInput() || isUnparsable || !hasQuotes) {
                                      return value;
                                  }
                                  module.debug('Encoding quote values for use in select', value);
                                  if (multipleValues) {
                                      $.each(value, function (index, value) {
                                          values.push(value.replace(regExp.quote, '&quot;'));
                                      });
                                      return values;
                                  }
                                  return value.replace(regExp.quote, '&quot;');
                              },
                              regExp: function (text) {
                                  text = String(text);
                                  return text.replace(regExp.escape, '\\$&');
                              }
                          },

                          setting: function (name, value) {
                              module.debug('Changing setting', name, value);
                              if ($.isPlainObject(name)) {
                                  $.extend(true, settings, name);
                              }
                              else if (value !== undefined) {
                                  if ($.isPlainObject(settings[name])) {
                                      $.extend(true, settings[name], value);
                                  }
                                  else {
                                      settings[name] = value;
                                  }
                              }
                              else {
                                  return settings[name];
                              }
                          },
                          internal: function (name, value) {
                              if ($.isPlainObject(name)) {
                                  $.extend(true, module, name);
                              }
                              else if (value !== undefined) {
                                  module[name] = value;
                              }
                              else {
                                  return module[name];
                              }
                          },
                          debug: function () {
                              if (!settings.silent && settings.debug) {
                                  if (settings.performance) {
                                      module.performance.log(arguments);
                                  }
                                  else {
                                      module.debug =
                                      Function.prototype.bind.call(console.info, console,
                                                                   settings.name + ':');
                                      module.debug.apply(console, arguments);
                                  }
                              }
                          },
                          verbose: function () {
                              if (!settings.silent && settings.verbose && settings.debug) {
                                  if (settings.performance) {
                                      module.performance.log(arguments);
                                  }
                                  else {
                                      module.verbose =
                                      Function.prototype.bind.call(console.info, console,
                                                                   settings.name + ':');
                                      module.verbose.apply(console, arguments);
                                  }
                              }
                          },
                          error: function () {
                              if (!settings.silent) {
                                  module.error =
                                  Function.prototype.bind.call(console.error, console,
                                                               settings.name + ':');
                                  module.error.apply(console, arguments);
                              }
                          },
                          performance: {
                              log: function (message) {
                                  var
                                      currentTime,
                                      executionTime,
                                      previousTime
                                      ;
                                  if (settings.performance) {
                                      currentTime = new Date().getTime();
                                      previousTime = time || currentTime;
                                      executionTime = currentTime - previousTime;
                                      time = currentTime;
                                      performance.push({
                                                           'Name': message[0],
                                                           'Arguments': [].slice.call(message, 1)
                                                                        || '',
                                                           'Element': element,
                                                           'Execution Time': executionTime
                                                       });
                                  }
                                  clearTimeout(module.performance.timer);
                                  module.performance.timer =
                                  setTimeout(module.performance.display, 500);
                              },
                              display: function () {
                                  var
                                      title = settings.name + ':',
                                      totalTime = 0
                                      ;
                                  time = false;
                                  clearTimeout(module.performance.timer);
                                  $.each(performance, function (index, data) {
                                      totalTime += data['Execution Time'];
                                  });
                                  title += ' ' + totalTime + 'ms';
                                  if (moduleSelector) {
                                      title += ' \'' + moduleSelector + '\'';
                                  }
                                  if ((console.group !== undefined || console.table !== undefined)
                                      && performance.length > 0) {
                                      console.groupCollapsed(title);
                                      if (console.table) {
                                          console.table(performance);
                                      }
                                      else {
                                          $.each(performance, function (index, data) {
                                              console.log(
                                                  data['Name'] + ': ' + data['Execution Time']
                                                  + 'ms');
                                          });
                                      }
                                      console.groupEnd();
                                  }
                                  performance = [];
                              }
                          },
                          invoke: function (query, passedArguments, context) {
                              var
                                  object = instance,
                                  maxDepth,
                                  found,
                                  response
                                  ;
                              passedArguments = passedArguments || queryArguments;
                              context = element || context;
                              if (typeof query == 'string' && object !== undefined) {
                                  query = query.split(/[\. ]/);
                                  maxDepth = query.length - 1;
                                  $.each(query, function (depth, value) {
                                      var camelCaseValue = (depth != maxDepth)
                                              ? value + query[depth + 1].charAt(0).toUpperCase()
                                                + query[depth
                                                        + 1].slice(1)
                                              : query
                                          ;
                                      if ($.isPlainObject(object[camelCaseValue]) && (depth
                                                                                      != maxDepth)) {
                                          object = object[camelCaseValue];
                                      }
                                      else if (object[camelCaseValue] !== undefined) {
                                          found = object[camelCaseValue];
                                          return false;
                                      }
                                      else if ($.isPlainObject(object[value]) && (depth
                                                                                  != maxDepth)) {
                                          object = object[value];
                                      }
                                      else if (object[value] !== undefined) {
                                          found = object[value];
                                          return false;
                                      }
                                      else {
                                          module.error(error.method, query);
                                          return false;
                                      }
                                  });
                              }
                              if ($.isFunction(found)) {
                                  response = found.apply(context, passedArguments);
                              }
                              else if (found !== undefined) {
                                  response = found;
                              }
                              if ($.isArray(returnedValue)) {
                                  returnedValue.push(response);
                              }
                              else if (returnedValue !== undefined) {
                                  returnedValue = [returnedValue, response];
                              }
                              else if (response !== undefined) {
                                  returnedValue = response;
                              }
                              return found;
                          }
                      };

                      if (methodInvoked) {
                          if (instance === undefined) {
                              module.initialize();
                          }
                          module.invoke(query);
                      }
                      else {
                          if (instance !== undefined) {
                              instance.invoke('destroy');
                          }
                          module.initialize();
                      }
                      $.fn.dropdown.all.push({"id": module.get.id(), "module": module});
                  })
        ;
        return (returnedValue !== undefined)
            ? returnedValue
            : $allModules
            ;
    };
    $.fn.dropdown.all = [];

    $.fn.dropdown.find = function (id) {
        var module;
        $.each($.fn.dropdown.all, function (i, n) {
            if (n.id === id) {
                module = n.module;
                return false;
            }
        });
        return module;
    };

    $.fn.dropdown.settings = {

        silent: false,
        debug: false,
        verbose: false,
        performance: true,

        on: 'click',    // what event should show menu action on item selection
        action: 'activate', // action on item selection (nothing, activate, select, combo, hide,
                            // function(){})

        apiSettings: false,
        selectOnKeydown: true,       // Whether selection should occur automatically when keyboard
                                     // shortcuts used
        minCharacters: 0,          // Minimum characters required to trigger API call
        saveRemoteData: true,       // Whether remote name/value pairs should be stored in
                                    // sessionStorage to allow remote data to be restored on page
                                    // refresh
        throttle: 200,        // How long to wait after last user input to search remotely

        context: window,     // Context to use when determining if on screen
        direction: 'auto',     // Whether dropdown should always open in one direction
        keepOnScreen: true,       // Whether dropdown should check whether it is on screen before
                                  // showing

        match: 'both',     // what to match against with search selection (both, text, or label)
        fullTextSearch: false,      // search anywhere in value (set to 'exact' to require exact
                                    // matches)

        placeholder: 'auto',     // whether to convert blank <select> values to placeholder text
        preserveHTML: true,       // preserve html when selecting value
        sortSelect: false,      // sort selection on init

        forceSelection: true,       // force a choice on blur with search selection

        allowAdditions: false,      // whether multiple select should allow user added values
        hideAdditions: true,      // whether or not to hide special message prompting a user they
                                  // can enter a value

        maxSelections: false,      // When set to a number limits the number of selections to this
                                   // count
        useLabels: true,       // whether multiple select should filter currently active selections
                               // from choices
        delimiter: ',',        // when multiselect uses normal <input> the values will be delimited
                               // with this character

        showOnFocus: true,       // show menu on focus
        allowReselection: false,      // whether current value should trigger callbacks when
                                      // reselected
        allowTab: true,       // add tabindex to element
        allowCategorySelection: false,      // allow elements with sub-menus to be selected

        fireOnInit: false,      // Whether callbacks should fire when initializing dropdown values

        transition: 'auto',     // auto transition will slide down or up based on direction
        duration: 200,        // duration of transition

        glyphWidth: 1.037,      // widest glyph width in em (W is 1.037 em) used to calculate
                                // multiselect input width

        // label settings on multi-select
        label: {
            transition: 'scale',
            duration: 200,
            variation: false
        },

        // delay before event
        delay: {
            hide: 300,
            show: 200,
            search: 20,
            touch: 50
        },

        /* Callbacks */
        onChange: function (value, text, $selected) {
        },
        onAdd: function (value, text, $selected) {
        },
        onRemove: function (value, text, $selected) {
        },

        onLabelSelect: function ($selectedLabels) {
        },
        onLabelCreate: function (value, text) {
            return $(this);
        },
        onLabelRemove: function (value) {
            return true;
        },
        onNoResults: function (searchTerm) {
            return true;
        },
        onShow: function () {
        },
        onHide: function () {
        },

        afterSelected: function () {
        },
        /* Component */
        name: 'Dropdown',
        namespace: 'dropdown',

        message: {
            addResult: 'Add <b>{term}</b>',
            count: '{count} selected',
            maxSelections: 'Max {maxCount} selections',
            noResults: 'No results found.',
            serverError: 'There was an error contacting the server'
        },

        error: {
            action: 'You called a dropdown action that was not defined',
            alreadySetup: 'Once a select has been initialized behaviors must be called on the created ui dropdown',
            labels: 'Allowing user additions currently requires the use of labels.',
            missingMultiple: '<select> requires multiple property to be set to correctly preserve multiple values',
            method: 'The method you called is not defined.',
            noAPI: 'The API module is required to load resources remotely',
            noStorage: 'Saving remote data requires session storage',
            noTransition: 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>'
        },

        regExp: {
            escape: /[-[\]{}()*+?.,\\^$|#\s]/g,
            quote: /"/g
        },

        metadata: {
            defaultText: 'defaultText',
            defaultValue: 'defaultValue',
            placeholderText: 'placeholder',
            text: 'text',
            value: 'value'
        },

        // property names for remote query
        fields: {
            remoteValues: 'results',  // grouping for api results
            values: 'values',   // grouping for all dropdown values
            disabled: 'disabled', // whether value should be disabled
            name: 'name',     // displayed dropdown text
            value: 'value',    // actual dropdown value
            text: 'text'      // displayed text when selected
        },

        keys: {
            backspace: 8,
            delimiter: 188, // comma
            deleteKey: 46,
            enter: 13,
            escape: 27,
            pageUp: 33,
            pageDown: 34,
            leftArrow: 37,
            upArrow: 38,
            rightArrow: 39,
            downArrow: 40
        },

        selector: {
            addition: '.addition',
            dropdown: '.ui.dropdown',
            hidden: '.hidden',
            icon: '> .dropdown.icon',
            input: '> input[type="hidden"], > select',
            item: '.item',
            label: '> .label',
            remove: '> .label > .delete.icon',
            siblingLabel: '.label',
            menu: '.menu',
            message: '.message',
            menuIcon: '.dropdown.icon',
            search: 'input.search, .menu > .search > input, .menu input.search',
            sizer: '> input.sizer',
            text: '> .text:not(.icon)',
            unselectable: '.disabled, .filtered'
        },

        className: {
            active: 'active',
            addition: 'addition',
            animating: 'animating',
            disabled: 'disabled',
            empty: 'empty',
            dropdown: 'ui dropdown',
            filtered: 'filtered',
            hidden: 'hidden transition',
            item: 'item',
            label: 'ui label',
            loading: 'loading',
            menu: 'menu',
            message: 'message',
            multiple: 'multiple',
            placeholder: 'default',
            sizer: 'sizer',
            search: 'search',
            selected: 'selected',
            selection: 'selection',
            upward: 'upward',
            visible: 'visible'
        }

    };

    /* Templates */
    $.fn.dropdown.settings.templates = {

        // generates dropdown from select values
        dropdown: function (select) {
            var
                placeholder = select.placeholder || false,
                values = select.values || {},
                html = ''
                ;
            html += '<i class="dropdown icon"></i>';
            if (select.placeholder) {
                html += '<div class="default text">' + placeholder + '</div>';
            }
            else {
                html += '<div class="text"></div>';
            }
            html += '<div class="menu">';
            $.each(select.values, function (index, option) {
                html += (option.disabled)
                    ? '<div class="disabled item" data-value="' + option.value + '">' + option.name
                      + '</div>'
                    : '<div class="item" data-value="' + option.value + '">' + option.name
                      + '</div>'
                ;
            });
            html += '</div>';
            return html;
        },

        // generates just menu from select
        menu: function (response, fields) {
            //resource
            var
                values = response[fields.values] || {},
                html = ''
                ;
            $.each(values, function (index, option) {
                var
                    maybeText = (option[fields.text])
                        ? 'data-text="' + option[fields.text] + '"'
                        : '',
                    maybeDisabled = (option[fields.disabled])
                        ? 'disabled '
                        : ''
                    ;
                html +=
                '<div class="' + maybeDisabled + 'item" data-value="' + option[fields.value]
                + '"' + maybeText;
                //将其他数据加载进行存储
                if (option.data != undefined && option.data.length != 0) {
                    html +=
                    ' other-data="' + $.encodeUnicode(JSON.stringify(option.data)) + '" ';
                }
                html += '">';
                html += option[fields.name];
                html += '</div>';
            });
            return html;
        },

        // generates label for multiselect
        label: function (value, text) {
            return text + '<i class="delete icon"></i>';
        },

        // generates messages like "No results"
        message: function (message) {
            return message;
        },

        // generates user addition to selection menu
        addition: function (choice) {
            return choice;
        }

    };

})(jQuery, window, document);
